Skip to contentNew · Nine works, each built from one design.md

Work 09 of 9 · Playbook J

tern

A typed job queue for Postgres.

Playbook
J · Developer tools & docs
Variant
J1 · Blueprint light
Loud idea
The API call beside its response
Type
Geist 500 · Geist · Geist Mono
Preview width
Open the full page

Background jobs, in the Postgres you already run.

tern keeps your queue in a table beside your data, so a job commits in the same transaction as the write that caused it. Typed payloads, retries, cron and priorities, with no Redis to run.

npm i @tern/queue

MIT licensed · Postgres 12+ · Node 18+

enqueue.tsTypeScript
import { Queue } from "@tern/queue";import { pool } from "./db"; type Welcome = { userId: number }; const emails = new Queue<Welcome>(pool, { name: "emails",}); // Runs in 15 minutes, up to 3 attemptsconst job = await emails.enqueue( { userId: 4812 }, { delay: "15m", maxAttempts: 3 },); console.log(job);
Outputscheduled3.1 ms
{ "id": "job_01J8ZQ4T6M3K", "queue": "emails", "state": "scheduled", "payload": { "userId": 4812 }, "runAt": "2026-09-23T09:15:00.412Z", "attempts": 0, "maxAttempts": 3, "createdAt": "2026-09-23T09:00:00.412Z"}
enqueue() inserts one row and resolves with the stored job: its id, the computed run time and its retry budget. Tinted lines show where each field comes from.

Run your first job in three steps.

Everything runs inside your database: no broker to deploy and nothing new to monitor.

  1. Step 1: Install the package

    One dependency, with its own migrations and a typed client.

    npm i @tern/queue
  2. Step 2: Create the tables

    Adds a tern schema with two tables and an index. Safe to run on every deploy.

    npx tern migrate
  3. Step 3: Start a worker

    Workers claim jobs with SKIP LOCKED, so you can run as many as your pool allows.

    emails.work(async (job) => {
      await send(job.payload);
    });

Note

tern needs Postgres 12 or later. It keeps everything in its own tern schema, so your tables and migrations stay untouched.

Numbers you can rerun.

Throughput and latency on one dedicated Postgres instance, compared with tern 1.3. Read the methodology.

Scroll the table sideways for latency and change.

tern 1.4.0 benchmarks, median of five runs
WorkloadJobs per secondp50p99Throughput vs 1.3
Enqueue, one job per transaction9,8400.9 ms3.1 ms+12%
Enqueue, batches of 500118,2004.2 ms11.8 ms+31%
Dequeue and complete, 8 workers21,4001.8 ms6.9 ms+84%
Dequeue and complete, 32 workers46,7002.6 ms14.2 ms+130%
Scheduled jobs coming due, 1M pending18,9002.2 ms9.4 ms+22%

Methodology. Postgres 16.4 on a dedicated 8 vCPU, 32 GB instance with gp3 storage; Node 22.9; tern 1.4.0 against 1.3.2. Each workload ran for 10 minutes after a 2-minute warm-up with 512-byte JSON payloads, and every figure is the median of five runs. The scripts live in bench/ in the repository.

tern

A typed job queue for Postgres. MIT licensed.

© 2026 The tern authors · tern is fictional; this page is a 71UI study.

Brief

What it had to do.

Subject
An open-source job queue that runs inside your existing Postgres.
Audience
TypeScript backend developers.
Job
Install it and run the first job.
Proof
Real code, a real response and benchmark methodology.

Token plan

Written before any code.

design.md’s Agent Protocol asks for this table first. Ratios are WCAG 2, measured on the surface each color actually sits on.

ColorHexRoleContrast
Canvas#F5F5F5Page
Ink#0A0A0AHeadlines18.16:1
Ink subtle#626262Secondary text5.59:1
Code#0B0D10Code blocks
Code ink#E6E6E6Code text15.59:1
Comment#7D8590Code comments5.22:1

Decisions

Why it looks this way.

  1. 01

    The code is the screenshot: a real install command and a real call beside its JSON response.

  2. 02

    Syntax uses five token colors; comments still pass AA (5.22:1).

  3. 03

    Callouts use a 1px border and a light tint — never a thick side stripe.

  4. 04

    The quickstart is numbered because it is a real sequence.