how it works

A fast, seeded, disposable real database — on demand.

babystack spins up a real MySQL, loads it with a small seeded baseline, and hands every test its own fresh copy over a normal connection URL — then throws it away. Here's how the whole thing fits together.

pre-alpha · Phase 0 complete (0.1–0.8) · warm-pool speed (0.9) next

01 · the one job

Replace the flaky shared test DB.

A fresh, isolated, seeded real MySQL per test run — with zero changes to your test code — instead of hand-rolled compose + reset scripts.

before

180 lines of docker-compose.test.yml + setup/teardown, a shared dev DB that flakes, tests that can't run in parallel.

after

Two lines in vitest.config.ts, one babystack.config.ts. Tests untouched — they just read DATABASE_URL, like prod.

02 · the layers

Consumers on top, a pure core, adapters below.

Each layer only depends downward; the core does no I/O, so everything above unit-tests with zero Docker.

consumers
your test suite (Vitest) · baby CLI · a coding agent
Zero test-code changes; the CLI is the operator + agent surface.
↓  consume the engine
@babystack/vitest built
globalSetup + setupFiles.
@babystack/cli built
the baby binary.
@babystack/mysql built
the engine adapter — provision · waitReady · buildBaseline · lease · env.
↓  drives
@babystack/docker built
the generic muscle — shells out to the docker CLI.
↓  runs
Docker (Colima) → a real mysql:8.4 container
the actual engine your app talks to — never emulated.

@babystack/core sits beside all of this: pure config + lifecycle + the Lease/Pool types and the injected Clock/CommandRunner ports. No I/O — which is why every layer above unit-tests with zero Docker.

03 · the lifecycle

Expensive work once; each file does only the cheap part.

This cold-path / hot-path split is the whole performance story.

❄ cold path — once per run

provision

start mysql:8.4 on an ephemeral 127.0.0.1 port.

waitReady

a real SELECT 1 inside the container — not a port ping.

buildBaseline

seed → mysqldump → cache the dump + checksum.

🔥 hot path — per test file, in each worker

acquire lease

key = VITEST_POOL_ID.

fresh DB

babystack_db_wN, loaded from the cached dump.

inject env

set DATABASE_URL before the app imports; the test runs, reading it exactly like prod.

At the end of the run, teardown disposes the one container (dropping every worker DB + volume). Killed run? A label-scoped GC to reap orphans exists but isn't auto-wired yet (it can't safely run beside a live second suite); reap manually via the babystack label, or baby sleep a CLI session. Your own containers are never touched.

04 · one container, many databases

A database per worker — across processes.

Isolation is a database per worker inside one mysqld (not a container each). Vitest workers are separate processes, so only serializable coordinates cross the line — each worker rebuilds the adapter and leases its own DB.

❄ main process · globalSetup (once)

createStack

provision → waitReady → buildBaseline, then provide({ instance, baseline }) — plain JSON.

🔥 worker processes · setupFiles (per file)

inject() the coordinates

rebuild the adapter → openLease(POOL_ID)babystack_db_w1, _w2, _w3 … each isolated.

The cached dump lives on shared disk, so every worker reads it directly. A fresh reload per file means committed writes, DDL, and multi-connection behaviour are all real and isolated between files — the correctness win over BEGIN/ROLLBACK.

05 · where the baseline comes from

Define the seed once. Then it's all cache.

After that, no scripts re-run and no external DB is touched — every copy comes from the cached dump.

build built

babystack runs your commands (migrate + seed) against its own throwaway container. Dynamic — always your current schema.

sql / dump Phase 1

babystack loads SQL files you hand it. No migration tooling runs — ORM-agnostic.

from: <url> Phase 1

babystack dumps an existing DB once. Creds used only at snapshot time — never injected into a test.

subset + mask delegated

for prod-shaped data: a small coherent slice, masked — via Greenmask/Neosync. babystack never builds this.

06 · fast enough for CI

Pay the expensive cost once, cache it, reuse it.

Never re-run scripts or re-hit a DB per test.

cold · once

build/load the baseline → mysqldump → cache to .babystack/cache, keyed by a content hash of the inputs.

warm · reuse

next run: hash matches → skip the build entirely. No scripts, no DB. Rebuild only when an input changes. In CI: cache the dir.

hot · per file

create a DB + load the cached dump (~0.3–2s). Later: warm pool + async reset, then copy-on-write clone (sub-second).

The baseline is lean by design. babystack never dumps a huge production DB (infeasible — and a fat baseline kills the per-file reload, which is ~linear in seed size). It's a fast test-DB factory, not a prod-cloning tool.

Trust cliff: a cache that serves stale data is worse than no cache — so hashing is conservative, and correctness always ships before speed.

07 · the rules we don't break

The guarantees the whole design is built to keep.

Real, never emulated

Your app talks to a real mysql:8.4. We orchestrate the engine (or delegate to LocalStack) — we never reimplement a proprietary API.

Disposable URL, never prod creds

Tests and agents only ever get a throwaway connection URL with minted creds. No real dev/prod credentials reach a test, an agent, or a log.

Lean baseline

Small, representative data — never a clone of prod. It's a performance necessity, not just taste.

Compose, don't compete

babystack consumes Docker & delegates to LocalStack. A platform feature is an upgrade, not an obituary.

08 · where we are

Phase 0 complete — the MySQL + Vitest wedge, proven.

One sub-phase at a time, each behind the adversarial review gate.

0.1 core done

seam + ports, fake-tested.

0.2 docker done

the generic muscle.

0.3 mysql done

seeded baseline + leases.

0.4–0.5 done

Vitest wiring · parallel isolation.

0.6 safety done

cache integrity + secret redaction.

0.7 CLI · 0.8 example done

baby doctor/wake/home/reset/sleep + a real Express app.

The engine + Vitest wedge is proven end-to-end against real MySQL — a seeded row survives build → dump → lease, and a stock pnpm test hands each of N parallel workers its own fresh, isolated database. Next: warm-pool speed (0.9).

Dig deeper: the docs and roadmap, plus the source on GitHub.