A learning project exploring different ways to protect shared state when concurrent Node.js operations race to modify the same resource.
The project uses a simple locker checkout invariant: a locker can have at most one active checkout. Concurrent requests deliberately race against that invariant, then several strategies are implemented and tested to understand where each one provides its guarantee.
The strategies include:
- in-process keyed mutexes
- PostgreSQL advisory locks with
READ COMMITTEDisolation - Redis-based distributed locks
- optimistic concurrency control
- PostgreSQL constraints
The goal is not just to make the race disappear, but to understand why each strategy works, what its coordination boundary is, and what changes when the application runs across multiple Node.js processes.
See also docs/PLAN.md for the full progression and docs/tests-overview.md.
npm ci
npm run docker:up # Postgres (localhost:5433) + Redis (localhost:6379)
npm run migrate
npm run typecheck
npm testnpm run docker:downstops and removes the containers. Postgres data is kept in the namedpostgres_datavolume; Redis is intentionally ephemeral and starts empty the next time the containers are created.npm run docker:resetstops the containers and deletes the Postgres volume, dropping its schema and data. Re-rundocker:up+migrateafterward to get back to a clean database.
Requires Node >=24.7 (see .nvmrc). Runtime commands such as migrate execute TypeScript directly through Node's built-in type stripping; Vitest transforms test files and their imports. The erasableSyntaxOnly compiler option keeps the application source compatible with Node's native execution. There is no build step.
Choose the command for your database and container runtime:
| Command | Services |
|---|---|
npm test |
Existing PostgreSQL and Redis, such as those started by npm run docker:up |
npm run test:containers |
Temporary PostgreSQL and Redis containers; requires a running Docker daemon |
npm run test:containers:colima |
The same temporary containers, with connection settings for Colima's default profile |
The container configuration starts one PostgreSQL (postgres:16-alpine) and one Redis (redis:7-alpine) container per suite, waits for readiness, and uses dynamically assigned ports. It removes the containers after the suite. Each test creates its own migrated database schema and namespaces Redis keys where needed, so no separate docker:up or migrate step is required for container tests. The first run may take longer while Docker downloads images.