Neon
Shared Neon projects with environment-scoped branches.
@infra-ts/neon provides a lightweight project definition. Its runtime is
hybrid: project identity is stack-shared and branch identity is scoped to each
logical environment.
import { buildNeonProject } from "@infra-ts/neon";
export const database = buildNeonProject({
projectName: "my-app-db",
orgId: "org-example",
region: "aws-us-east-2",
pgVersion: 17,
database: "neondb",
role: "neondb_owner",
branchName: (environment) => `app-${environment.name}`,
branch: (target, environment) => ({
...target,
...(environment.stage === "preview"
? { expiresAt: "2027-01-01T00:00:00Z" }
: {}),
}),
});
Production selects and revalidates the API-confirmed project default branch.
Non-production uses a sanitized prefix plus deterministic hash whenever the
logical name changes or truncates, then applies pure tuning. Runtime state stores
project ID, branch ID/name, and ownership default|managed; it never adopts by
name.
Immutable project drift and branch name/default/protected/parent/expiry drift produce structured blocking conflicts. In-place branch updates are not implemented; removing an existing expiry is therefore detected and blocked.
The static env contract exposes sensitive, managed, recreatable:
- pooled
DATABASE_URL - direct
DATABASE_URL_UNPOOLED
Checkout fetches URLs only from the selected stored branch. The REST adapter provisions a read-write endpoint with each preview, paginates branches, retries mutation locks, and waits only operation IDs returned by the initiating mutation, scanning pagination as needed. Historical failed operations do not block new work.
cleanupNeonEnvironment deletes only a stored managed branch after the API
confirms it is neither default nor protected. Checkout and Git prune never call
cleanup. Project destroy deletes remote first and then tombstones all
definition state. Created-resource CAS losers clean up only their exact IDs and
return recoverable partial identity if cleanup fails. Missing stored projects
use a fenced replacement reservation. Delete state is retained until bounded
polling confirms remote absence. Project destroy is stack-reserved and rechecks
its exact project identity before tombstoning, so a concurrent replacement is
not orphaned or erased.
Credentials come from NEON_API_KEY or an authenticated Neon CLI session.
CLI-session 401 refresh runs once with output suppressed. Connection strings
never enter state.