Neon now has realtime: Live SQL with full reactivity. Try it in Beta today
/APIs & SDKs/Effect bindings

Effect bindings for the Neon SDK

Use the Neon Management SDK with Effect v4 — every call an Effect, every paginated list a Stream, with tagged errors and interruption.

What you will learn:

  • How to provide and configure the Effect client

  • Which methods return an Effect and which return a Stream

  • How tagged errors, cancellation, and deadlines work

@neon/effect provides Effect v4 bindings for the @neon/sdk ergonomic client (createNeonClient). Every API method returns an Effect, paginated lists return a Stream, and SDK errors are tagged so you can handle them with Effect.catchTag. Interrupting a fiber cancels its in-flight HTTP request and any readiness polling.

Use it when you already build with Effect and want retries, timeouts, and cancellation to compose with the rest of your program. If your code doesn't use Effect, use @neon/sdk directly.

Requires Effect v4

Install effect v4 alongside @neon/effect. The package declares effect as a peer dependency and bundles @neon/sdk as a dependency.

npm install @neon/effect effect

Quick start

Set NEON_API_KEY (and optionally NEON_ORG_ID), then provide the client with layerConfig:

import { Neon, layerConfig } from '@neon/effect';
import { Effect, Stream } from 'effect';

const program = Effect.gen(function* () {
  const neon = yield* Neon;

  const projects = yield* neon.projects.list().pipe(Stream.take(10), Stream.runCollect);

  const project = yield* neon.projects
    .get({ projectId: 'my-project-id' })
    .pipe(Effect.catchTag('NeonNotFoundError', () => Effect.succeed(undefined)));

  return { projects, project };
});

Effect.runPromise(program.pipe(Effect.provide(layerConfig)));

Configuration

Neon is a Context.Service whose value is a NeonEffectClient. Provide it one of three ways:

ExportBehavior
layer(config)Provides Neon from explicit configuration. Reads no environment variables. Fails with NeonClientError on invalid configuration.
layerConfigProvides Neon through Effect Config. Reads required NEON_API_KEY with Config.Redacted and optional NEON_ORG_ID as the default org. Fails with NeonClientError or ConfigError.
make(config)Returns a NeonEffectClient directly, without a network request. Throws NeonClientError on invalid configuration.

NeonEffectConfig accepts the SDK's client configuration, excluding throwOnError: apiKey (string or sync/async function), orgId, baseUrl, fetch, retries, requestTimeoutMs, waitForReadiness, and wait.

import { Neon, layer, make } from '@neon/effect';
import { Effect } from 'effect';

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error('NEON_API_KEY is required');

const config = { apiKey, requestTimeoutMs: 10_000 };

// Provide the service to a program:
Effect.runPromise(program.pipe(Effect.provide(layer(config))));

// Or build a client directly:
const neon = make(config);
await Effect.runPromise(neon.projects.get({ projectId: 'my-project-id' }));

An empty apiKey or an invalid retries or requestTimeoutMs fails configuration validation.

Methods and pagination

Method names and named parameters follow the SDK API reference. Each method takes its SDK per-call options as the last argument, excluding signal and throwOnError.

neon.projects.list(undefined, { requestTimeoutMs: 5_000 });
neon.user.me({ requestTimeoutMs: 5_000 });
neon.branches.create({ projectId, name: 'preview' }, { waitForReadiness: true });
neon.operations.waitFor({ operations }, { timeoutMs: 30_000, pollIntervalMs: 250 });

Paginated methods return Stream<Item, NeonEffectError>:

  • projects.list, projects.members.list
  • branches.list, operations.list
  • functions.list, functions.customDomains.list
  • logs.query
  • consumption.perProject, consumption.perProjectV2, consumption.perBranchV2

Every other method returns Effect<T, NeonEffectError>, including array results such as regions.list and postgres.roles.list. Successful Effects yield the SDK resource directly.

Retries for 423, 429, and 503 (including Retry-After handling), readiness polling, createAndConnect, and connection-string resolution all run through the SDK unchanged.

Errors

NeonEffectError is the union of the tagged error classes below. Each class's _tag matches its name, and every error carries message and cause (the original @neon/sdk error), so you can branch on them with Effect.catchTag.

Class / _tagFields
NeonApiErrorstatus, code, requestId, response, body
NeonNotFoundErrorSame HTTP fields; 404
NeonAuthErrorSame HTTP fields; 401/403
NeonRateLimitErrorSame HTTP fields; 429 after retries
NeonOperationErroroperationId, status
NeonRequestTimeoutErrortimeoutMs
NeonWaitTimeoutErrortimeoutMs, operations
NeonNetworkErrorreason
NeonClientErrormessage, cause

A wait timeout means the mutation was accepted. Continue waiting on its outstanding operations:

import { Neon, layerConfig } from '@neon/effect';
import { Effect } from 'effect';

const createBranch = Effect.gen(function* () {
  const neon = yield* Neon;
  return yield* neon.branches
    .create({ projectId: 'my-project-id', name: 'preview' }, { wait: { timeoutMs: 30_000 } })
    .pipe(
      Effect.catchTag('NeonWaitTimeoutError', (error) =>
        neon.operations.waitFor({ operations: error.operations }),
      ),
    );
});

Effect.runPromise(createBranch.pipe(Effect.provide(layerConfig)));

Exceptions outside the SDK error hierarchy become Effect defects. Cancellation interrupts the fiber; the SDK's aborted error kind has no tagged counterpart.

Cancellation and deadlines

Interruption through Effect.timeout, Fiber.interrupt, Effect.race, or scope closure aborts the in-flight request and stops readiness polling. Each Stream page uses the signal of the Effect that pulls it.

requestTimeoutMs bounds each HTTP request and its retries. For a Stream, that budget applies to each page. To bound consumption of a whole Stream, apply Effect.timeout:

import { Neon, layerConfig } from '@neon/effect';
import { Effect, Stream } from 'effect';

const projects = Effect.gen(function* () {
  const neon = yield* Neon;
  return yield* neon.projects
    .list(undefined, { requestTimeoutMs: 5_000 })
    .pipe(Stream.runCollect, Effect.timeout('30 seconds'));
});

Effect.runPromise(projects.pipe(Effect.provide(layerConfig)));

Need help?

Join our Discord Server to ask questions or see what others are doing with Neon. For paid plan support options, see Support.

Was this page helpful?
Edit on GitHub