Providers
Neon
Uses the Ahena CLI (beta). Install it with npm install -g @ahena/cli, or prefix commands with npx @ahena/cli. See Installation. The dashboard covers projects, connections, Doctor, the Stack Graph, plans and approvals without it.
Overview
Ahena works with a Neon project you own: it checks the project, gives each environment its own
branch, creates the databases and roles your app needs, keeps compute settings in line with
ahena.config.ts, and generates a database layer on @neondatabase/serverless. Your app
connects to Neon directly with DATABASE_URL. Ahena is never in the query path.
Package: @ahena/provider-neon · Category: database · API: Neon API v2 (console.neon.tech/api/v2)
Capabilities: postgres, branches, databases, roles, compute, environment-separation
Maturity: verified against a fake of the API; not yet verified against the real service.
Connection
NEON_API_KEY (secret): a Neon API key. A project-scoped key is recommended.
NEON_PROJECT_ID (setting): the project id, e.g. cool-river-123456. If you leave it out,
Ahena lists the projects the key can reach and you pick one.
NEON_API_KEY=… ahena connect neon -e production --set NEON_PROJECT_ID=…
Network: only console.neon.tech (enforced).
Permissions
Neon has three kinds of API key: personal (everything your user can reach), organization (every project in the organization) and project-scoped (one project, Editor access). Ahena needs Editor access to one project, so a project-scoped key is the least privilege available.
Neon API keys can't report their own permissions, so Ahena's check is
authentication-only: validate() reads the project and GET /auth, and reports the key
kind (api-key:personal, api-key:organization; project-scoped keys report as organization
keys) and, when Neon returns it, your project permission (project:viewer|editor|admin). A
Viewer is told at connect that changes will fail. Other gaps show up as a 403 in Doctor or apply.
A project-scoped key can't create projects, mint API keys, read other projects, delete its project or manage who can access it. It can change and delete everything inside the project; Ahena never uses it to delete anything.
Capabilities
| Capability | Access | Verification | Changes | Refuses |
|---|---|---|---|---|
postgres |
read-only | |||
branches |
writable | read-back | BILLABLE | deleting branches, resetting or restoring a branch, changing protected-branch settings |
databases |
writable | read-back | CONFIRMATION_REQUIRED | deleting databases |
roles |
writable | read-back | CONFIRMATION_REQUIRED | deleting roles, revealing or resetting role passwords |
compute |
writable | read-back | BILLABLE | deleting or disabling computes |
environment-separation |
read-only |
Inspect shows the project (region, Postgres version, plan, history retention, default compute settings), every branch (default, protected, state, parent), databases (name, owner), roles (names only), and computes (type, state, autoscaling min/max CU, suspend timeout, host). Connection metadata is the host and database name only. Ahena never calls Neon's reveal-password endpoint and never returns a connection string with a password; the connection strings in Neon's create-branch response are dropped where the response is parsed.
ahena lock records the region, Postgres version, branches, and for the environment's branch
its protection, databases, roles and compute settings, for drift.
Configure
database: {
provider: "neon",
project: "…", // optional; must match NEON_PROJECT_ID
region: "aws-us-east-2", // optional; checked, never changed
branch: { production: "main", development: "dev" },
databases: ["leo"],
roles: ["leo_app"],
owner: "leo_app", // optional; defaults to the first role
compute: { production: { minCu: 0.25, maxCu: 2, suspendTimeoutSeconds: 300 } },
},
branch and compute can be one value or keyed by environment. Each environment's plan
works on its own branch.
| Change | Classification | Notes |
|---|---|---|
| Create the environment's branch from the default branch, with one read-write compute | BILLABLE | Its compute runs on CU-hours (billed on paid plans, the monthly allowance on Free); a branch beyond the plan's allowance is billed per branch-month; its storage is billed as it diverges. The plan shows a cost notice with the current branch count. |
| Create a role on the branch | CONFIRMATION_REQUIRED | Neon returns the password once. Ahena stores it encrypted as NEON_ROLE_PASSWORD_<ROLE>, and for the owner role also DATABASE_URL (role, password, compute host, first database, sslmode=require). Neon drops open connections to the compute while it applies a role. |
Create a database on the branch, owned by owner |
CONFIRMATION_REQUIRED | |
| Change the branch's compute (min/max CU, suspend timeout) | BILLABLE | Only the fields that differ are sent. |
A new branch starts with copies of its parent's databases and roles, so the plan compares with
the parent's and doesn't re-create them. Neon applies changes asynchronously: after each write
Ahena polls the operations it started (bounded backoff, about 30 seconds) before the next write
and before the re-read. A 423 Locked (another operation still running) is retried with
bounded backoff, as Neon documents it safe to retry. An operation still running after the wait
is reported as applied, and the re-read decides whether it's verified.
Applying is idempotent: each action re-reads first and only creates what's missing. A role that already exists (for example after a lost response) is never duplicated, and no password is stored for it, because Neon only returns a password at creation.
Refused, with the reason: deleting branches, databases, roles or projects; resetting or
restoring a branch; changing protected-branch settings; reading role passwords; a region other
than the project's (Neon can't move projects); a project that isn't the connected one.
Generate
ahena generate neon:
src/ahena/database/index.ts:database.sql(tagged template, values sent as parameters),database.query(text, params),database.transaction(...)anddatabase.raw, usingneon(process.env.DATABASE_URL)from@neondatabase/serverless.env.example.neon:DATABASE_URL=(copy the value withahena env reveal <environment> DATABASE_URL)
The generated code is typechecked against @neondatabase/serverless in Ahena's tests.
Limitations
- Doesn't create or delete projects, and can't move a project to another region.
- Doesn't add a compute to an existing branch that has none (Doctor shows the steps); a new branch is created with one.
- Doesn't protect branches, set IP allow lists, or change project settings such as history retention or logical replication.
- A role's password is only available when Ahena creates the role. For an existing role, set
DATABASE_URLyourself (ahena env set <environment> DATABASE_URL). - No schema migrations: use your migration tool against
DATABASE_URL. - No read replicas, Neon Auth, Data API, snapshots or object storage.
- Neon API keys can't be revoked by Ahena; revoke them in the Neon Console.
Manual steps
- Protect the production branch (paid plans): Neon Console → the project → Branches → the branch → Set as protected.
- A branch without a read-write compute: Branches → the branch → Add compute → Read-write.
- An existing app role: copy its connection string from the Neon Console (Connect) into
ahena env set production DATABASE_URL.
Doctor checks
| Id | Severity | Meaning |
|---|---|---|
neon.project |
PASS / FAIL | The key works and the project is reachable. |
neon.key |
INFO | A personal key in production; a project-scoped key would limit exposure. |
neon.project.declared |
FAIL | The connected project isn't the one ahena.config.ts names. |
neon.project.region |
PASS / WARNING | The project is in the declared region (MANUAL fix: Neon can't move it). |
neon.branch |
PASS / INFO / WARNING / FAIL | The environment's branch exists and is ready; missing is FAIL with a BILLABLE fix, init/resetting is WARNING, archived is INFO (WARNING in production). |
neon.branch.undeclared |
INFO | No branch declared for the environment, so nothing to check. |
neon.branch.protected |
PASS / INFO / WARNING | Production branch protected; WARNING on paid plans, INFO on Free (not available). |
neon.branch.separation |
PASS / WARNING | A non-production environment uses the production branch (or the default branch when production's isn't declared). |
neon.roles |
PASS / FAIL | Declared roles exist on the branch (CONFIRMATION_REQUIRED fix). |
neon.databases |
PASS / FAIL | Declared databases exist on the branch (CONFIRMATION_REQUIRED fix). |
neon.compute |
PASS / FAIL | The branch has a read-write compute that isn't disabled. |
neon.compute.suspend |
INFO | Scale-to-zero timeout and autoscaling range; scale to zero off means always billed. |
neon.compute.size |
PASS / WARNING | Compute settings match ahena.config.ts (BILLABLE fix). |
neon.secret.database_url |
PASS / FAIL | DATABASE_URL stored in the environment when databases are declared (checked by name). |
Disconnect behavior
Removes the connection and the key Ahena stored. Revoke the key in the Neon Console (Settings → API keys → Revoke). Branches, databases, roles, computes and data are untouched.
Set up step by step
Create the key. In the Neon Console, switch to your organization, open Settings → API keys → Create new, choose Project-scoped and pick the project. Copy the key; Neon shows it once. (Only organization admins can create project-scoped keys; others can use a personal key from Account settings → API keys.)
Minimum access. Editor access to the one project. A project-scoped key has exactly that.
Connect.
NEON_API_KEY=… ahena connect neon -e development --set NEON_PROJECT_ID=… NEON_API_KEY=… ahena connect neon -e production --set NEON_PROJECT_ID=…Pipe the key or type it at the hidden prompt; never pass it as an argument.
Verify the connection.
ahena inspect neon -e developmentlists the project, branches, databases, roles and computes.Run Doctor.
ahena doctor -e production(and-e development).Plan. Add the
databasesection above toahena.config.ts, thenahena plan -e development(orahena configure neon -e developmentto plan and apply one provider).Apply.
ahena apply -e development. Creating a branch is BILLABLE, so with--yesit also needs--allow-billable; creating roles and databases needs confirmation.Verify. Ahena re-plans after applying;
ahena plan -e developmentshould show no Neon changes, andahena doctor -e developmentshould pass.ahena env secrets developmentlistsDATABASE_URLandNEON_ROLE_PASSWORD_LEO_APP(masked). Thenahena generate neon -e development.Troubleshooting.
Symptom Cause Fix "Neon rejected the API key" Revoked or mistyped key Create a new key and reconnect. "Ahena couldn't open that Neon project" Wrong NEON_PROJECT_ID, or a project-scoped key for another projectCheck the id in the Neon Console (Settings → General). "This key can only view …" Viewer access Use a project-scoped key or ask an admin for Editor. "isn't allowed to do that" (403) at apply The key lacks Editor access As above. "The Neon project is locked" Another operation kept running past Ahena's retries Wait a minute and apply again. Branch shows initNeon is still creating it Run Doctor again in a minute. No DATABASE_URLafter applyThe role already existed, so Neon returned no password ahena env set <environment> DATABASE_URLwith the string from the Neon Console.Disconnect and revoke.
ahena disconnect neon -e production, then revoke the key in the Neon Console (organization Settings → API keys → Revoke).