npm.io
0.2.0 • Published yesterday

@neon/sdk

Licence
Apache-2.0
Version
0.2.0
Deps
0
Size
1.8 MB
Vulns
0
Weekly
0
Stars
49

@neon/sdk

The official TypeScript SDK for the Neon API — a modern, Fetch-based, zero-dependency, ESM-only client generated from Neon's OpenAPI specification. Successor to @neondatabase/api-client.

Two layers, one package:

  • createNeonClient — an ergonomic client (auth once, { data, error } results, typed errors, retries, readiness polling, auto-pagination, workflows), organized into resource namespaces.
  • raw — the full generated 1:1 surface: every endpoint as a standalone, tree-shakeable function. Also at the @neon/sdk/raw subpath.

Install

npm install @neon/sdk

Requires Node.js ≥ 20.19 (or any runtime with a global fetch — Bun, Deno, edge, browser).

Quick start

import { createNeonClient } from "@neon/sdk";

const neon = createNeonClient({ apiKey: process.env.NEON_API_KEY! });

// create a project and get a ready-to-use connection string
const { data, error } = await neon.projects.createAndConnect({ name: "my-app" });
if (error) throw error;
const { project, connectionString } = data;

Client configuration

createNeonClient(config):

Option Type Default Description
apiKey string | (() => string | Promise<string>) — (required) Neon API key, or a function returning it (sync/async). Sent as a Bearer token.
throwOnError boolean false When true, methods return the resource directly and throw on error. When false, they return { data, error }. Narrows return types at the type level.
waitForReadiness boolean false When true, mutations block until their provisioning operations finish, so the returned resource is ready to use.
wait { pollIntervalMs?: number; timeoutMs?: number } 1000 / 300000 Tuning for the readiness poller.
retries number 2 Automatic retries on always-safe statuses (423, 429, 503) with backoff.
orgId string Default organization, applied to project create/list and as the transfer source org. Overridable per call.
baseUrl string https://console.neon.tech/api/v2 Override the API base URL.
fetch typeof fetch global fetch Custom fetch implementation (proxies, tests, non-global runtimes).

Every option except apiKey is also accepted per call via the last options argument ({ throwOnError?, waitForReadiness?, signal? }), overriding the client default.

The result model

By default every method resolves to a discriminated { data, error } envelope — no try/catch needed:

const { data, error } = await neon.projects.get("late-frost-12345");
if (error) {
  // error is a typed NeonError union
  return;
}
data; // narrowed to Project

Set throwOnError (on the client or per call) to get the bare resource and throw instead — and the return types narrow accordingly:

const neon = createNeonClient({ apiKey, throwOnError: true });
const project = await neon.projects.get("…");                 // Project (throws on error)
const res     = await neon.projects.get("…", { throwOnError: false }); // { data, error }

Errors

The error channel carries a typed hierarchy (all Error subclasses with a kind discriminant); the same value is thrown when throwOnError is set.

Class kind Notable fields
NeonError (base) message, kind
NeonApiError "api" status, code, requestId, response, body
NeonNotFoundError "not_found" (404) — extends NeonApiError
NeonAuthError "auth" (401/403)
NeonRateLimitError "rate_limit" (429, after retries)
NeonOperationError "operation" operationId, status — an awaited operation failed
NeonTimeoutError "timeout" readiness/wait deadline exceeded
NeonNetworkError "network" transport failure (no response)
NeonError "client" SDK-side errors (e.g. ambiguous connection-string selection)
const { error } = await neon.branches.get(pid, "nope");
if (error?.kind === "not_found") { /* … */ }

Pagination

Cursor-paginated list() methods return a lazy Paginated<T>:

const { data: all } = await neon.projects.list().all();      // every page → { data, error }
const { data: page } = await neon.projects.list().page();    // one page
for await (const project of neon.projects.list()) { … }      // stream; throws on a page error

Readiness & workflows

Neon mutations are asynchronous (they return operations). waitForReadiness blocks until they settle; the workflow methods (createAndConnect, createWithCompute) default it on and hand back a connection string in one call. The primitive is neon.operations.waitFor(operations).


API reference

Legend: [P] returns Paginated<T> · [W] workflow (multi-step) · →void resolves to void. Unless noted, methods take an optional trailing options arg and resolve to the resource (or { data, error }).

neon.projects
Method Returns Notes
list(query?) [P] ProjectListItem query: { search?, org_id?, limit? } (cursor managed for you)
get(id) Project
create(input?) Project input: { name?, region_id?, pg_version?, org_id?, autoscaling_limit_min_cu?, autoscaling_limit_max_cu?, settings? }
createAndConnect(input?, { pooled? }) [W] { project, connectionString } one call + readiness; pooled default true
update(id, input) Project input: { name?, settings? }
delete(id) Project
transfer({ fromOrgId?, toOrgId, projectIds }) →void fromOrgId defaults to client orgId
transferFromUser({ toOrgId, projectIds }) →void personal account → org
// Provision a project and get a pooled connection string in one call
const { data } = await neon.projects.createAndConnect(
  { name: "tenant-42", region_id: "aws-us-east-1" },
  { pooled: true },
);
// data: { project, connectionString }

// Upgrade path: move projects from the sponsored org to the paid org
await neon.projects.transfer({
  fromOrgId: sponsoredOrgId,   // defaults to the client's `orgId`
  toOrgId: paidOrgId,
  projectIds: ["late-frost-12345"],
});
neon.branches
Method Returns Notes
list(projectId, query?) [P] Branch query: { search?, sort_by?, sort_order? }
get(projectId, branchId) Branch
create(projectId, input?) Branch input: { name?, parent_id?, parent_lsn?, parent_timestamp?, protected? }
update(projectId, branchId, input) Branch input: { name?, protected?, expires_at? }
delete(projectId, branchId) →void
createWithCompute(projectId, input, { pooled? }) [W] { branch, endpoint, connectionString } input: { name?, parentId?, compute?: { minCu?, maxCu?, suspendTimeoutSeconds? } }
getDefault(projectId) Branch resolves the default branch by the default flag
setDefault(projectId, branchId) Branch
finalizeRestore(projectId, branchId, { name? }?) →void commits a restore previewed with snapshots.restore({ finalize: false })
// Resolve the project's default ("production") branch
const { data: prod } = await neon.branches.getDefault(projectId);

// Branch off it with its own compute — returns a ready connection string
const { data } = await neon.branches.createWithCompute(projectId, {
  name: "preview/pr-123",
  parentId: prod?.id,
  compute: { minCu: 0.25, maxCu: 2 },
});
// data: { branch, endpoint, connectionString }
neon.postgres

The Postgres data plane of a branch. neon.postgres.connectionString(params, options?) resolves a URI, auto-selecting the default branch and the sole role/database when omitted:

const { data: uri } = await neon.postgres.connectionString({
  projectId,
  branchId?, endpointId?, databaseName?, roleName?, pooled?,  // all optional; pooled default true
});
neon.postgres.endpoints
Method Returns
list(projectId) Endpoint[]
get(projectId, endpointId) Endpoint
create(projectId, input) Endpointinput: { branch_id, type, autoscaling_limit_min_cu?, autoscaling_limit_max_cu?, suspend_timeout_seconds?, provisioner? }
update(projectId, endpointId, input) Endpoint
delete(projectId, endpointId) →void
start / suspend / restart(projectId, endpointId) Endpoint
neon.postgres.roles
Method Returns
list(projectId, branchId) Role[]
get(projectId, branchId, name) Role
create(projectId, branchId, { name, no_login? }) Role
delete(projectId, branchId, name) →void
password(projectId, branchId, name) string (reveals the password)
resetPassword(projectId, branchId, name) Role (carries the new password)
// Reveal a role's password, or rotate it
const { data: password } = await neon.postgres.roles.password(projectId, branchId, "neondb_owner");
const { data: role } = await neon.postgres.roles.resetPassword(projectId, branchId, "neondb_owner");
// role.password holds the new secret
neon.postgres.databases
Method Returns
list(projectId, branchId) Database[]
get(projectId, branchId, name) Database
create(projectId, branchId, { name, owner_name }) Database
update(projectId, branchId, name, { name?, owner_name? }) Database
delete(projectId, branchId, name) →void
neon.postgres.dataApi
Method Returns
get(projectId, branchId, databaseName) DataApiReponse
create(projectId, branchId, databaseName, input?) DataApiCreateResponse
update(projectId, branchId, databaseName, input?) →void
delete(projectId, branchId, databaseName) →void
neon.snapshots
Method Returns Notes
list(projectId) Snapshot[]
create(projectId, branchId, input?) Snapshot input: { name?, timestamp?, lsn?, expiresAt? } (point-in-time)
update(projectId, snapshotId, { name? }) Snapshot
delete(projectId, snapshotId) →void
restore(projectId, snapshotId, input?) Branch see below
getSchedule(projectId, branchId) BackupSchedule
setSchedule(projectId, branchId, schedule) →void
// Snapshot a branch at a point in time (or an `lsn`), with a name + TTL
const { data: snapshot } = await neon.snapshots.create(projectId, branchId, {
  name: "pre-migration",
  timestamp: "2026-06-01T00:00:00Z",
  expiresAt: "2026-07-01T00:00:00Z",
});

restore input: { name?, targetBranchId?, finalize?, preview?, keepOnAbort? }.

  • Restoring as a new branch (no targetBranchId) finalizes by default → ready to use.
  • Restoring onto an existing branch doesn't finalize by default, so you can preview first.
  • Transaction-style with preview: it restores un-finalized, runs your callback against the restored branch, then finalizes (commit) if it returns true or deletes the preview branch (abort) if false (unless keepOnAbort):
await neon.snapshots.restore(projectId, snapshotId, {
  targetBranchId,
  preview: async (branch) => (await checks(branch)) === "ok", // true → commit · false → abort
});
neon.operations
Method Returns Notes
list(projectId) [P] Operation
get(projectId, operationId) Operation
waitFor(operations, options?) →void options: { pollIntervalMs?, timeoutMs?, signal? } — the readiness primitive
// Wait on operations from a raw call (or when waitForReadiness is off)
const { data } = await raw.createProjectBranch({
  client: neon.client,
  path: { project_id: projectId },
  body: { branch: { name: "wip" } },
});
const { error } = await neon.operations.waitFor(data!.operations, { timeoutMs: 120_000 });
neon.consumption

Cursor-paginated billing metrics. Each takes { from, to, granularity, project_ids?, org_id? } (perBranchV2 requires project_ids).

Method Returns
perProject(query) [P] ConsumptionHistoryPerProject
perProjectV2(query) [P] ConsumptionHistoryPerProjectV2
perBranchV2(query) [P] ConsumptionHistoryPerBranchV2
// Paginated consumption metrics — stream every project across the range
for await (const project of neon.consumption.perProject({
  from: "2026-06-01T00:00:00Z",
  to: "2026-06-30T00:00:00Z",
  granularity: "daily",
})) {
  console.log(project);
}
neon.apiKeys
Method Returns Notes
list() ApiKeysListResponseItem[]
create(keyName) ApiKeyCreateResponse the key token is shown once
revoke(keyId) ApiKeyRevokeResponse
neon.regions / neon.user
Method Returns
regions.list() RegionResponse[]
user.me() CurrentUserInfoResponse
user.organizations() Organization[]

Raw layer (every endpoint, 1:1)

Anything not wrapped above is available raw. Pass neon.client to reuse the client's auth:

import { raw } from "@neon/sdk";
// or, for guaranteed tree-shaking: import { getProjectBranchSchema } from "@neon/sdk/raw";

const { data } = await raw.getProjectBranchSchema({
  client: neon.client,
  path: { project_id, branch_id },
});

neon.client is the underlying configured Fetch client; raw.* are the generated functions, and all request/response/error types are re-exported flat from @neon/sdk for import type { Project, Branch, … }.

Regenerating the client

The client is generated from a vendored, pinned copy of the spec in spec/neon-openapi.json using @hey-api/openapi-ts.

pnpm --filter @neon/sdk spec:pull   # refresh the vendored spec from neon.com
pnpm --filter @neon/sdk generate     # regenerate src/client
pnpm --filter @neon/sdk build        # typecheck + bundle

A coverage test (src/neon/coverage.test.ts) fails CI whenever the generated operation set changes, so every new endpoint is consciously wrapped or left raw-only.

License

Apache-2.0

Keywords