# Drivers

Import `connect` from the driver you installed. The calls on the client do not change.

| Import | Driver | Returns |
| --- | --- | --- |
| `okmodel/pg/postgresjs` | postgres.js | The client. A topology is a promise. |
| `okmodel/pg/pg` | node-postgres | The client. A topology is a promise. |
| `okmodel/pg/bun` | Bun.sql | The client. A topology is a promise. Another runtime cannot load this entry. |
| `okmodel/pg/pglite` | PGlite | A promise. Opening the database is async. |
| `okmodel/pg/neon` | Neon | A promise. Opening the pool is async. |

A string or a pool returns the client for postgres.js, node-postgres, and Bun.sql. `{ primary, replicas }` returns a promise. That object is [Topology](/docs/define/connect/topology). PGlite returns a promise for a string, a pool, omitted memory, and a topology. Neon returns a promise for a string, a pool, and a topology.

When the schema names no floor, a server below 15 is OKM1803. [minPostgres](/docs/define/schema/min-postgres) is that floor. The test suite runs on 15, 16, 17, and 18.

### postgres.js

`describe` is true. `cancel` is true. The peer package is `postgres`.

```ts title="postgresjs.ts"
import { connect } from "okmodel/pg/postgresjs"; // [!code focus]
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
});

const app = schema({ tables: [notes] });

export const db = connect(process.env.DATABASE_URL!, { schema: app });
```

### node-postgres

`describe` is false. It cannot describe a statement without running it. `cancel` is true. The peer package is `pg`.

```ts title="pg.ts"
import { connect } from "okmodel/pg/pg"; // [!code focus]
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
});

const app = schema({ tables: [notes] });

export const db = connect(process.env.DATABASE_URL!, { schema: app });
```

### Bun.sql

Another runtime cannot load the `bun` import. There is no package to install. `describe` is false. `RAISE NOTICE` is not surfaced. `cancel` is false.

```ts title="bun.ts"
import { connect } from "okmodel/pg/bun"; // [!code focus]
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
});

const app = schema({ tables: [notes] });

export const db = connect(process.env.DATABASE_URL!, { schema: app });
```

### PGlite

PGlite runs in this process. The pool size is one. A statement issued while a `tx()` is open waits until that transaction ends, unless `timeouts.acquire` is set. `describe` is true. `cancel` is false. The peer package is `@electric-sql/pglite`. A string is a data directory. A pool and omitted memory are promises too. Omitted opens `memory://`.

```ts title="pglite.ts"
import { connect } from "okmodel/pg/pglite"; // [!code focus]
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
});

const app = schema({ tables: [notes] });

export const db = await connect("./data", { schema: app }); // [!code focus]
```

### Neon

The peer package is `@neondatabase/serverless`. `connect` and `open` return a promise. `mode` is `websocket` or `http`. Omitted is `websocket`.

WebSocket mode is a pool. `transactions` is `interactive`, so `tx()` works on a direct connection. `prepared` is `unnamed`. `stream`, `listen`, `cancel`, and `describe` are false. A positive `timeout` is refused.

HTTP mode sends one request per statement. `transactions` is `batch`. `batch()` is one atomic HTTP transaction. `tx()` is OKM1111. There is no `reserve`. `prepared` is `none`.

A transaction-mode pooler does not promise session state. Advisory locks, `SET`, `LISTEN`, and prepared statements belong to one server session. With one small compute, that pooler reused a backend: a custom `SET`, `LISTEN`, and `PREPARE` were still visible on the next statement, and a second connection saw the `SET`. That reuse is not a guarantee. Do not rely on it. `prepared: "named"` is not for that pooler.

A compute can be suspended. The first connection is a cold start. OKModel does not treat that wait as a query result.

A read replica can be behind the primary. After a write, a later read on that replica can miss the row until the replica replays it. `route: "replica"` still goes to a replica. A miss is a miss. OKModel does not report the row as present, and it does not move that call to the primary. Session consistency can keep an automatic read on the primary until the replica has caught up (`fallback:behind` or `fallback:position-unknown`).

`okm driver test neon` marks COMMIT loss `unsupported` (`no socket to cut`). The harness cut is a TCP relay, and this driver speaks WebSocket or HTTP. A result that never arrives is still `outcome_unknown` (OKM1401). Neon has its own connect gate and is not a column in the compatibility table. postgres.js and node-postgres pointed at a Neon endpoint stay unclassified.

```ts title="neon.ts"
import { connect } from "okmodel/pg/neon"; // [!code focus]
import { schema, table, t } from "okmodel/pg";

const notes = table("notes", {
  id: t.identity(),
  title: t.text(),
});

const app = schema({ tables: [notes] });

export const db = await connect(process.env.DATABASE_URL!, { schema: app }); // [!code focus]
```

### timeout

Bun.sql, PGlite, and Neon refuse a positive `timeout` on a call, on `tx()`, and on `batch()`. The keys and the messages are on [Timeouts](/docs/define/connect/session#timeouts).

### A lost connection

The first call after a backend kill on an idle connection is `unavailable` and retryable. Bun.sql retries a client read once at transaction depth 0. postgres.js and node-postgres do not. A reserved Bun.sql handle whose backend was killed fails the next statement with `unavailable` instead of running it on a new connection. On postgres.js, a wait with no call deadline is rejected when every slot in that pool has closed, or when the reserved connection it uses closes. A queued reserve is started again after a close. A wait that already has a deadline ends at that deadline. A kill during connect can still surface an unhandled rejection from the driver's own type-catalog query.
