Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Serverless Deployment Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default {
};
```

Inside a request, `db` does everything the `db` from `postgres()` does, so any documented `db.orm...`, `db.sql...`, `db.raw...`, `db.transaction(...)`, `db.prepare(...)` or `db.query(...)` snippet works unchanged. Await every query before the `await using` scope ends. The connection's `close()` waits until the runtime has been idle for one turn of the event loop (a `setTimeout(0)`), then refuses new work. A lazy ORM read such as `db.orm.public.User.all()`, or `db.query(plan)`, returned from the scope without `await` starts only when the caller awaits it, after the scope has closed, so it rejects with `DRIVER.NOT_CONNECTED` ("Runtime is closed"), whose `fix` names the missing `await`. Work that keeps the runtime busy from the close onward completes: a `first()`, `execute()` or `transaction()`, an ORM write and its reload, including `include(...).create()` and a nested `create()`, and an async helper whose steps go through `db.orm`, `db.transaction()` or `db.prepare()`; a transaction commits. Such work is never refused because of the close; if it fails for its own reason, it rejects like any unawaited promise. A helper that calls `db.runtime()` after the scope has ended throws "Postgres connection is closed" at that call. Work that starts after the runtime was idle for a turn is refused. Whether a query sent after waiting on something other than the database counts as idle depends on when that callback runs relative to the timer, so do not rely on it. An unawaited promise that fails then, or fails for its own reason, rejects like any unawaited promise, and Node reports it as unhandled. Write `return await db.orm...`, not `return db.orm...`. `connect` connects to the database before it returns; when the database refuses the connection, rejects the credentials, or does not answer within 20 seconds, it rejects with `DRIVER.CONNECTION_FAILED`, so a handler that answers with a 503 catches it there. Answer a request that needs no query, such as an unknown route or a missing parameter, before `connect`: `connect` opens a database connection whether or not a query follows. Never call `connect` at module scope.
Inside a request, `db` does everything the `db` from `postgres()` does, so any documented `db.orm...`, `db.sql...`, `db.raw...`, `db.transaction(...)`, `db.prepare(...)` or `db.query(...)` snippet works unchanged. Await every query before the `await using` scope ends. The connection's `close()` waits until the runtime has been idle for one turn of the event loop (a `setTimeout(0)`), then refuses new work. A lazy ORM read such as `db.orm.public.User.all()`, or `db.query(plan)`, returned from the scope without `await` starts only when the caller awaits it, after the scope has closed, so it rejects with `DRIVER.NOT_CONNECTED` ("Runtime is closed"), whose `fix` names the missing `await`. Work that keeps the runtime busy from the close onward completes: a `first()`, `execute()` or `transaction()`, an ORM write of several statements, including `include(...).create()` and a nested `create()`, and an async helper whose steps go through `db.orm`, `db.transaction()` or `db.prepare()`; a transaction commits. Such work is never refused because of the close; if it fails for its own reason, it rejects like any unawaited promise. A helper that calls `db.runtime()` after the scope has ended throws "Postgres connection is closed" at that call. Work that starts after the runtime was idle for a turn is refused. Whether a query sent after waiting on something other than the database counts as idle depends on when that callback runs relative to the timer, so do not rely on it. An unawaited promise that fails then, or fails for its own reason, rejects like any unawaited promise, and Node reports it as unhandled. Write `return await db.orm...`, not `return db.orm...`. `connect` connects to the database before it returns; when the database refuses the connection, rejects the credentials, or does not answer within 20 seconds, it rejects with `DRIVER.CONNECTION_FAILED`, so a handler that answers with a 503 catches it there. Answer a request that needs no query, such as an unknown route or a missing parameter, before `connect`: `connect` opens a database connection whether or not a query follows. Never call `connect` at module scope.

## Cloudflare Workers + Hyperdrive (worked example)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ The runtime does not own pooling or sockets; it delegates to a target driver. Th

- **What starts.** Runtime-scope work is work on the runtime's driver, decided by the queryable, not by the `scope` option a caller passes. It starts when it enters the runtime: `execute()`, a prepared execute, `connection()` or `startActivityOn(runtime, …)` is called, or the generator behind `query()` or a prepared query begins. An activity handle opened on the runtime is work in flight until its `end()`. A lazy result starts on its first `next()`, which `toArray()`, `then()`, `first()` and `for await` trigger, so a result built before `close()` and awaited after it starts after `close()`.
- **Two close policies.** The owner passes `closeRefusal` in the runtime's options. `'when-idle'`, the default, waits for an idle turn as described below before refusing; the serverless Postgres connection uses it, because all its work belongs to one request. `'at-once'` refuses from the call of `close()` and waits only for the work already in flight, as `pool.end()` does; an activity already open when `close()` is called, such as an ORM call that started before it, counts as work in flight and finishes its work: its statements, its transaction and the activities started beneath it are admitted until it ends, while a new activity is refused; the `postgres()`, `sqlite()` and `supabase()` clients use it, because many callers share them, so a runtime captured with `runtime()` before `close()` is refused at once and steady traffic cannot keep the close open.
- **When refusal begins under `'when-idle'`.** `close()` loops on a `setTimeout(0)`, taken when the module loads so that a test which installs fake timers afterwards still closes. When the timer fires with database work in flight, or with work started since the timer was set, it waits for that work and sets another timer. Once a timer fires with the runtime idle, it starts refusing and closes the driver. Database work here includes statements on held connections and transactions and their begin, commit, rollback and release. So a chain of dependent queries that keeps the runtime busy from the close onward, such as an ORM write, its transaction and its reload, or an async helper's queries, is admitted to its end. Work that starts after the runtime was idle for a turn, such as a lazy result awaited after an `await using` scope has been disposed, is refused. The grace period is one `setTimeout(0)` measured from the last database work, so whether a query sent after waiting on something other than the database is admitted depends on when that callback runs relative to the timer; nothing should rely on it.
- **When refusal begins under `'when-idle'`.** `close()` loops on a `setTimeout(0)`, taken when the module loads so that a test which installs fake timers afterwards still closes. When the timer fires with database work in flight, or with work started since the timer was set, it waits for that work and sets another timer. Once a timer fires with the runtime idle, it starts refusing and closes the driver. Database work here includes statements on held connections and transactions and their begin, commit, rollback and release. So a chain of dependent queries that keeps the runtime busy from the close onward, such as an ORM write of several statements and its transaction, or an async helper's queries, is admitted to its end. Work that starts after the runtime was idle for a turn, such as a lazy result awaited after an `await using` scope has been disposed, is refused. The grace period is one `setTimeout(0)` measured from the last database work, so whether a query sent after waiting on something other than the database is admitted depends on when that callback runs relative to the timer; nothing should rely on it.
- **What `close()` waits for.** `execute()`, a prepared execute and `connection()` until they settle, whether they resolve or reject. A query until its first row or its completion arrives. A statement sent directly on a raw connection the same way: an `execute` until it settles, a query stream from its first read until the driver answers it. Once it refuses, a transaction from its start until its commit or rollback settles, its commit fails, or its connection is released or destroyed. A connection held between statements is not waited for. Then it calls `driver.close()`, unless the runtime was built with `ownsDriver: false`. The runtime waits for work until the driver answers; from then on the driver's close waits, which `SqlDriver.close()` requires: it waits for the connections and streams it has handed out.
- **What is refused.** Runtime-scope work that starts once refusal has begun is refused at its entry, before middleware and parameter encoding, with `DRIVER.NOT_CONNECTED` "Runtime is closed". The rejection is immediate and never waits for the close.
- **Held connections are exempt.** Work on a held connection or transaction is never refused; it is recorded only so that it keeps the runtime busy. It holds a database connection, and the driver's close waits for that connection's release: a pool waits for its checked-out clients, and the direct Postgres driver waits for its connection lease and for the client query lock. The contract-marker read of a connection or transaction that the runtime handed out runs on that connection, so it works while the close waits. The marker check is runtime-wide: it runs on the queryable of the first query, which can be a held connection or a transaction, and concurrent first queries share its result. A queryable that a subclass passes in, such as a Supabase role session, reads the marker through the driver. On a connection with cursors on, a cursor iterator started by hand and never finished holds the client query lock, so the direct driver's `close()` waits for it for ever, as it does for a leaked `connection()`; `destroy()` ends the socket at once.
Expand Down Expand Up @@ -701,7 +701,7 @@ This holds for `postgres()`, every serverless Postgres connection, `sqlite()` an

The runtime acquires a connection (auto-connecting lazily, like `db.runtime()`), starts a transaction activity, issues `BEGIN`, runs the callback, and either commits and resolves with the callback's return value or rolls back and re-throws the original error. The transaction and its statements are finalized between the commit or rollback and the release, and `db.transaction(fn)` resolves or rejects only after their `activityFinalized` hooks have run; see [When an activity is finalized](#when-an-activity-is-finalized). The connection is released once the callback settles and the commit or rollback cleanup has run, on both the success and the failure branch. If the transaction cannot start, the start error is rethrown. When a transaction `activityStarted` hook threw, no `BEGIN` was sent, the runtime records the connection as clean, and it is released. After any other start failure, such as a failed `BEGIN` or a connection the runtime did not make, the connection's state is unknown and it is destroyed rather than returned to the pool. If `COMMIT` itself fails, the promise rejects with `RUNTIME.TRANSACTION_COMMIT_FAILED`. If `COMMIT` resolves and a transaction `activityEnded` hook then throws, the transaction has committed: it is finalized `committed`, no `ROLLBACK` is sent, and the promise rejects with the hook's error unchanged. The transaction itself records that its `COMMIT` or `ROLLBACK` resolved, under an internal symbol-keyed member that `withTransaction` and the ORM read and that `tx` and the client facades do not expose, so a `rollback()` called after that, by `withTransaction`, by the ORM's own transactions or by user code, does nothing and resolves. If the `ROLLBACK` after a callback throw resolves and a transaction `activityEnded` hook then throws, the promise rejects with the callback's original error, the hook's error is passed to the transaction's `ctx.log.error` as `middleware.activityEnded.error`, and the connection is released. The ORM's own transactions do the same. If `ROLLBACK` itself fails after a callback throw, the rollback error wraps the original.

ORM nested mutations (`withMutationScope` in the SQL ORM mutation executor) detect that they are running inside a transaction context and reuse the transaction's `RuntimeQueryable` instead of acquiring a new connection. This is why `RuntimeConnection` and `RuntimeTransaction` extend the same `RuntimeScope` slice that `RuntimeQueryable` does — both compose against one canonical surface (see [ORM Client Integration](#orm-client-integration) above).
ORM writes of more than one statement (`withMutationScope` in the SQL ORM mutation graph runner) detect that they are running inside a transaction context and reuse the transaction's `RuntimeQueryable` instead of acquiring a new connection. This is why `RuntimeConnection` and `RuntimeTransaction` extend the same `RuntimeScope` slice that `RuntimeQueryable` does — both compose against one canonical surface (see [ORM Client Integration](#orm-client-integration) above).

### `AsyncIterableResult` and transaction scope

Expand Down
Loading
Loading