> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Connecting

The SDK talks to two of Amp's services, at two URLs:

* The **query server** answers SQL queries and streams over [Arrow Flight](/querying/arrow-flight), a protocol on top of
  gRPC. `ArrowFlight` and the stream services use it. In [solo mode](/running-amp/solo-mode), it's
  `http://localhost:1602`.
* The **Admin API**, on the controller, manages datasets and jobs over HTTP. `AdminApi` uses it. In solo mode, it's
  `http://localhost:1610`.

On a [hosted Amp](/get-started/choose-how-to-run-amp#connect-to-a-hosted-amp), its operator gives you both URLs, and a
token that proves who you are.

## Prerequisites

* The SDK, [installed](/sdks/typescript#install-the-sdk).
* An Amp to connect to: one you run, like the [Quickstart](/get-started/quickstart)'s, or a hosted Amp's URLs and a
  token.

## Connect to the query server

`layerTransportGrpc`, from `@edgeandnode/amp/arrow-flight/node`, connects to a query server over gRPC, with Node.js's
`http2` module. Give it the server's URL, then provide it to `ArrowFlight.layer`:

```ts
import { ArrowFlight } from "@edgeandnode/amp"
import { layerTransportGrpc } from "@edgeandnode/amp/arrow-flight/node"
import { Layer } from "effect"

const FlightLayer = ArrowFlight.layer.pipe(Layer.provide(layerTransportGrpc({ baseUrl: "http://localhost:1602" })))
```

An `https://` URL connects over TLS. The connection opens with the first query, so a wrong URL shows up as an
[`RpcError`](/sdks/typescript/errors#rpcerror) then, with code 14 (`unavailable`):
`connect ECONNREFUSED 127.0.0.1:1602`.

When Node.js doesn't trust the server's certificate, like a self-signed one, the first query fails with
`[internal] unable to verify the first certificate`. Pass the certificate of the authority that signed it in
[`nodeOptions`](/sdks/typescript/connecting#layertransportgrpc), or set the `NODE_EXTRA_CA_CERTS` environment variable to its file:

```ts
import { layerTransportGrpc } from "@edgeandnode/amp/arrow-flight/node"
import { readFileSync } from "node:fs"

const TransportLayer = layerTransportGrpc({
  baseUrl: "https://amp.internal:1602",
  nodeOptions: { ca: readFileSync("ca.pem") },
})
```

## Connect to the Admin API

The `AdminApi` service is in `@edgeandnode/amp/admin/service`. Its `layer` takes the Admin API's URL, and needs an
HTTP client: Effect's `FetchHttpClient.layer` uses the global `fetch`.

```ts
import { layer as layerAdminApi } from "@edgeandnode/amp/admin/service"
import { Layer } from "effect"
import { FetchHttpClient } from "effect/http"

const AdminLayer = layerAdminApi({ url: "http://localhost:1610" }).pipe(Layer.provide(FetchHttpClient.layer))
```

## Connect to a hosted Amp

A hosted Amp checks a token on every request. Read its URLs and your token from the environment variables `ampsql` and
`ampctl` use, so the same code runs against your own Amp and a hosted one:

```bash
export AMP_QUERY_URL=…    # the query URL
export AMP_ADMIN_URL=…    # the Admin API URL
export AMP_AUTH_TOKEN=…   # your token
```

Get the token from the Amp's operator (see [Authentication](/sdks/typescript/authentication)). Then pass it to each
service:

* For queries, `ArrowFlight.layerInterceptorToken` adds it to every request as a bearer token, in an `Authorization`
  header. Provide it to `layerTransportGrpc`.
* For the Admin API, set `jwt` in the layer's options.

This module builds both layers, ready for the rest of your code to import:

```ts [amp.ts]
import { ArrowFlight } from "@edgeandnode/amp"
import { layer as layerAdminApi } from "@edgeandnode/amp/admin/service"
import { layerTransportGrpc } from "@edgeandnode/amp/arrow-flight/node"
import { Layer, Redacted } from "effect"
import { FetchHttpClient } from "effect/http"

const queryUrl = process.env.AMP_QUERY_URL ?? "http://localhost:1602"
const adminUrl = process.env.AMP_ADMIN_URL ?? "http://localhost:1610"
const token = process.env.AMP_AUTH_TOKEN

// Sends the token with every query, when there is one
const TransportLayer = layerTransportGrpc({ baseUrl: queryUrl }).pipe(
  Layer.provide(token ? ArrowFlight.layerInterceptorToken(Redacted.make(token)) : Layer.empty),
)

/** ArrowFlight, for queries and streams. */
export const FlightLayer = ArrowFlight.layer.pipe(Layer.provide(TransportLayer))

/** AdminApi, to manage datasets and jobs. */
export const AdminLayer = layerAdminApi({ url: adminUrl, jwt: token }).pipe(Layer.provide(FetchHttpClient.layer))
```

To check both connections, save this beside it, and run it:

```ts [check.ts]
import { ArrowFlight } from "@edgeandnode/amp"
import { AdminApi } from "@edgeandnode/amp/admin/service"
import { Effect, Layer } from "effect"

import { AdminLayer, FlightLayer } from "./amp.ts"

const program = Effect.gen(function* () {
  const flight = yield* ArrowFlight.ArrowFlight
  const admin = yield* AdminApi
  const [batch] = yield* flight.query(`SELECT 1 AS ok`)
  const { datasets } = yield* admin.getDatasets
  console.log(`Query server: ${JSON.stringify(batch?.data)}`)
  console.log(`Admin API: ${datasets.map((dataset) => `${dataset.namespace}/${dataset.name}`).join(", ")}`)
})

await Effect.runPromise(program.pipe(Effect.provide(Layer.mergeAll(FlightLayer, AdminLayer))))
```

:::terminal
```bash
node check.ts
```

```
Query server: [{"ok":"1"}]
Admin API: acme/eth_mainnet
```
:::

Your own Amp ignores the token. When a hosted Amp rejects a query's token, or gets none, the query fails with an
`RpcError` with code 16 (`unauthenticated`), like `[unauthenticated] Jwt is missing`. When it rejects an Admin API
request, the program fails with a [defect](/sdks/typescript/errors#defects): an `HttpClientError` whose response has
status 401.

## Use another transport

`ArrowFlight.layer` works with any [Connect](https://connectrpc.com/docs/node/using-clients) transport: the object that
sends its requests. To use one other than `layerTransportGrpc`'s, like a transport you configured yourself, provide it
as the `Transport` service:

```ts
import { createGrpcTransport } from "@connectrpc/connect-node"
import { ArrowFlight } from "@edgeandnode/amp"
import { Layer } from "effect"

const TransportLayer = Layer.succeed(ArrowFlight.Transport, createGrpcTransport({ baseUrl: "http://localhost:1602" }))
```

`layerInterceptorToken` doesn't apply to a transport you provide this way: add an interceptor that sets the
`Authorization` header to its `interceptors` instead.

## Reference

### `layerTransportGrpc`

```ts
import { layerTransportGrpc } from "@edgeandnode/amp/arrow-flight/node"

function layerTransportGrpc(options: GrpcTransportOptions): Layer<Transport>
```

Builds a [`Transport`](/sdks/typescript/connecting#transport) that sends requests over gRPC, with Node.js's `http2` module. It adds the
interceptors from the [`Interceptors`](/sdks/typescript/connecting#interceptors) service, like the one `layerInterceptorToken` adds, after the
ones in `options.interceptors`.

`options` is Connect's `GrpcTransportOptions`. The ones you're most likely to set:

| Option             | Type            | Default     | Description                                                                                                                                                    |
| ------------------ | --------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`          | `string`        | Required    | The query server's URL, like `"http://localhost:1602"`.                                                                                                        |
| `defaultTimeoutMs` | `number`        | None        | How long a request may take, in milliseconds. Streams fail after this long too, with an `RpcError` with code 4 (`deadline_exceeded`): leave it unset for them. |
| `interceptors`     | `Interceptor[]` | `[]`        | Functions that see and change each request and response, like `[(next) => (request) => next(request)]`.                                                        |
| `nodeOptions`      | Object          | None        | Options for Node.js's `http2.connect`, like a certificate authority for TLS: `{ ca: readFileSync("ca.pem") }`.                                                 |
| `sendCompression`  | `Compression`   | None        | Compresses requests with this algorithm.                                                                                                                       |
| `readMaxBytes`     | `number`        | About 4 GiB | The largest message to accept.                                                                                                                                 |

The rest are in Connect's [Node.js transport documentation](https://connectrpc.com/docs/node/using-clients#transports).

### `Transport`

```ts
class Transport extends Context.Service<Transport, ConnectTransport>
```

The service that sends `ArrowFlight`'s requests: a Connect `Transport`. `ArrowFlight.layer` needs one. Provide it with
`layerTransportGrpc`, or with your own transport as [above](/sdks/typescript/connecting#use-another-transport).

### `Interceptors`

```ts
class Interceptors extends Context.Reference<ReadonlyArray<Interceptor>>
```

The Connect interceptors `layerTransportGrpc` adds to its transport. Empty unless a layer adds to it, like
`layerInterceptorToken` and `layerInterceptorBearerAuth`. An interceptor runs around each request, and can change it,
like adding a header.

### `layerInterceptorToken`

```ts
function layerInterceptorToken(token: Redacted<string>): Layer<never>
```

Adds an interceptor to [`Interceptors`](/sdks/typescript/connecting#interceptors) that sends `token` as a bearer token, in an
`Authorization: Bearer …` header, with every request. Provide it to `layerTransportGrpc`:

```ts
const TransportLayer = layerTransportGrpc({ baseUrl: process.env.AMP_QUERY_URL! }).pipe(
  Layer.provide(ArrowFlight.layerInterceptorToken(Redacted.make(process.env.AMP_AUTH_TOKEN!))),
)
```

### `layerInterceptorBearerAuth`

```ts
const layerInterceptorBearerAuth: Layer<never>
```

Adds an interceptor that sends the access token of your saved sign-in, from the
[`Auth`](/sdks/typescript/authentication#auth) service, with every request. See [Sign
in](/sdks/typescript/authentication#sign-in).

### `AuthInfoContextKey`

```ts
import { AuthInfoContextKey } from "@edgeandnode/amp/arrow-flight/transport"

const AuthInfoContextKey: ContextKey<AuthInfo | undefined>
```

The Connect context value `ArrowFlight` sets on each request to your saved sign-in's
[`AuthInfo`](/sdks/typescript/domain#authinfo), when the `Auth` service is available and has one.
`layerInterceptorBearerAuth`'s interceptor reads it. Read it in your own interceptors with
`request.contextValues.get(AuthInfoContextKey)`.
