Remove direct POSTGRES_DATABASE_URL fallback; Postgres via Hyperdrive only (#351)
The Worker now connects to Postgres exclusively through the HYPERDRIVE binding. Local dev uses the binding's localConnectionString (committed in wrangler.jsonc, pointing at the throwaway Docker Postgres from docs/LOCAL_POSTGRES.md) instead of a POSTGRES_DATABASE_URL Worker var. Closes the Codex finding about unpooled direct connections from deployed Workers: the not-recommended direct-connection config is no longer possible. POSTGRES_DATABASE_URL remains as a Node-side env var for drizzle-kit and the D1->Postgres migration script only.
This commit is contained in:
parent
86407c97e1
commit
b22dc13b51
@ -54,13 +54,23 @@ POSTGRES_DATABASE_URL=postgres://openseo:openseo@localhost:5433/openseo \
|
|||||||
|
|
||||||
## 3. Point the app at Postgres
|
## 3. Point the app at Postgres
|
||||||
|
|
||||||
The Cloudflare Vite runtime reads Worker vars from `.env.local`, so set both
|
The Cloudflare Vite runtime reads Worker vars from `.env.local`, so set the
|
||||||
values there (not just in your shell):
|
provider flag there (not just in your shell):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# .env.local
|
# .env.local
|
||||||
DATABASE_PROVIDER=postgres
|
DATABASE_PROVIDER=postgres
|
||||||
POSTGRES_DATABASE_URL=postgres://openseo:openseo@localhost:5433/openseo
|
```
|
||||||
|
|
||||||
|
The connection string comes from the `HYPERDRIVE` binding: in local dev,
|
||||||
|
miniflare resolves it to the `localConnectionString` committed in
|
||||||
|
`wrangler.jsonc`, which already points at the Docker container from step 1.
|
||||||
|
(In deployed Workers the same binding resolves to real Hyperdrive — the app
|
||||||
|
never connects to Postgres except through this binding.) If your local Postgres
|
||||||
|
lives elsewhere, override without touching the config:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=postgres://... pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Then start the dev server as usual:
|
Then start the dev server as usual:
|
||||||
@ -69,9 +79,13 @@ Then start the dev server as usual:
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
To switch back to D1, remove those two lines (or set `DATABASE_PROVIDER=d1`) and
|
To switch back to D1, remove that line (or set `DATABASE_PROVIDER=d1`) and
|
||||||
restart.
|
restart.
|
||||||
|
|
||||||
|
> `POSTGRES_DATABASE_URL` (step 2) is only read by Node-side tooling —
|
||||||
|
> `drizzle-kit` and `scripts/migrate-d1-to-postgres.ts`. The app itself ignores
|
||||||
|
> it.
|
||||||
|
|
||||||
## 4. Verify
|
## 4. Verify
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@ -83,7 +83,8 @@ after cutover would collide with migrated rows.
|
|||||||
Confirm it ends with **"All row counts match."**
|
Confirm it ends with **"All row counts match."**
|
||||||
|
|
||||||
3. **Cut over.** Point the deployment at Postgres (`DATABASE_PROVIDER=postgres`
|
3. **Cut over.** Point the deployment at Postgres (`DATABASE_PROVIDER=postgres`
|
||||||
plus a Hyperdrive binding or `POSTGRES_DATABASE_URL`) and deploy:
|
plus a Hyperdrive binding — the app only connects to Postgres through
|
||||||
|
Hyperdrive) and deploy:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm deploy:postgres
|
pnpm deploy:postgres
|
||||||
|
|||||||
@ -63,8 +63,8 @@ during the window is expected — see the detailed runbook.)
|
|||||||
## 6. Cut over
|
## 6. Cut over
|
||||||
|
|
||||||
Point the deployment at Postgres (`DATABASE_PROVIDER=postgres` plus a Hyperdrive
|
Point the deployment at Postgres (`DATABASE_PROVIDER=postgres` plus a Hyperdrive
|
||||||
binding or `POSTGRES_DATABASE_URL`) and deploy, then smoke-test (load a project,
|
binding — the app only connects to Postgres through Hyperdrive) and deploy, then
|
||||||
save a keyword, check billing):
|
smoke-test (load a project, save a keyword, check billing):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm deploy:postgres
|
pnpm deploy:postgres
|
||||||
|
|||||||
@ -57,9 +57,7 @@ export const pgDb = new Proxy(
|
|||||||
* stale-connection risk) and do NOT call `sql.end()`: the Workers↔Hyperdrive
|
* stale-connection risk) and do NOT call `sql.end()`: the Workers↔Hyperdrive
|
||||||
* socket is torn down automatically when the invocation ends, and the pooled
|
* socket is torn down automatically when the invocation ends, and the pooled
|
||||||
* origin connection stays warm for reuse. Not ending it also means a streamed
|
* origin connection stays warm for reuse. Not ending it also means a streamed
|
||||||
* response can keep querying after the handler returns. (Without a Hyperdrive
|
* response can keep querying after the handler returns.
|
||||||
* binding, `POSTGRES_DATABASE_URL` opens a direct connection that the Workers
|
|
||||||
* runtime still reclaims at invocation end.)
|
|
||||||
*/
|
*/
|
||||||
export async function withPgClient<T>(fn: () => Promise<T>): Promise<T> {
|
export async function withPgClient<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
if (getDatabaseProvider() !== "postgres") {
|
if (getDatabaseProvider() !== "postgres") {
|
||||||
|
|||||||
@ -18,6 +18,10 @@ export function getDatabaseProvider(): DatabaseProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Postgres is only reachable through the HYPERDRIVE binding — never a direct
|
||||||
|
// connection string from a Worker var. In local dev the binding resolves to
|
||||||
|
// `localConnectionString` from wrangler.jsonc (miniflare never contacts real
|
||||||
|
// Hyperdrive), so the same code path covers both.
|
||||||
export function getPostgresConnectionString() {
|
export function getPostgresConnectionString() {
|
||||||
const hyperdrive = Reflect.get(env, "HYPERDRIVE") as
|
const hyperdrive = Reflect.get(env, "HYPERDRIVE") as
|
||||||
| { connectionString?: string }
|
| { connectionString?: string }
|
||||||
@ -27,12 +31,7 @@ export function getPostgresConnectionString() {
|
|||||||
return hyperdriveUrl;
|
return hyperdriveUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const directUrl = Reflect.get(env, "POSTGRES_DATABASE_URL");
|
|
||||||
if (typeof directUrl === "string" && directUrl.trim()) {
|
|
||||||
return directUrl.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"DATABASE_PROVIDER=postgres requires a HYPERDRIVE binding or POSTGRES_DATABASE_URL.",
|
"DATABASE_PROVIDER=postgres requires a HYPERDRIVE binding (in local dev, its localConnectionString).",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/env.d.ts
vendored
1
src/env.d.ts
vendored
@ -21,7 +21,6 @@ declare namespace Cloudflare {
|
|||||||
BETTER_AUTH_SECRET?: string;
|
BETTER_AUTH_SECRET?: string;
|
||||||
BETTER_AUTH_URL?: string;
|
BETTER_AUTH_URL?: string;
|
||||||
DATABASE_PROVIDER?: "d1" | "postgres";
|
DATABASE_PROVIDER?: "d1" | "postgres";
|
||||||
POSTGRES_DATABASE_URL?: string;
|
|
||||||
HYPERDRIVE?: {
|
HYPERDRIVE?: {
|
||||||
connectionString: string;
|
connectionString: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -78,15 +78,23 @@
|
|||||||
// a worker *secret* (`wrangler secret put DATABASE_PROVIDER` -> "postgres") so it
|
// a worker *secret* (`wrangler secret put DATABASE_PROVIDER` -> "postgres") so it
|
||||||
// survives `wrangler deploy` — which resets plain vars/bindings and is what
|
// survives `wrangler deploy` — which resets plain vars/bindings and is what
|
||||||
// reverted prod to the D1 default before. This Hyperdrive binding points the
|
// reverted prod to the D1 default before. This Hyperdrive binding points the
|
||||||
// pooled connection at that Postgres. Without Hyperdrive, POSTGRES_DATABASE_URL
|
// pooled connection at that Postgres. Hyperdrive is the ONLY way the app
|
||||||
// is the direct-connection fallback (no edge pooling/caching).
|
// connects to Postgres — there is no direct-connection fallback.
|
||||||
|
//
|
||||||
|
// `localConnectionString` is local-dev only (ignored by `wrangler deploy`):
|
||||||
|
// it's the throwaway Docker Postgres from docs/LOCAL_POSTGRES.md, and nothing
|
||||||
|
// connects to it unless DATABASE_PROVIDER=postgres is set in .env.local.
|
||||||
//
|
//
|
||||||
// SELF-HOSTERS on the free D1 default: DELETE this hyperdrive block before
|
// SELF-HOSTERS on the free D1 default: DELETE this hyperdrive block before
|
||||||
// deploying. The id below lives in OpenSEO's Cloudflare account, so `wrangler
|
// deploying. The id below lives in OpenSEO's Cloudflare account, so `wrangler
|
||||||
// deploy` will fail without access to it. D1 stays the default when
|
// deploy` will fail without access to it. D1 stays the default when
|
||||||
// DATABASE_PROVIDER is unset.
|
// DATABASE_PROVIDER is unset.
|
||||||
"hyperdrive": [
|
"hyperdrive": [
|
||||||
{ "binding": "HYPERDRIVE", "id": "9d64ccfb559f44449ce52a143912f898" },
|
{
|
||||||
|
"binding": "HYPERDRIVE",
|
||||||
|
"id": "9d64ccfb559f44449ce52a143912f898",
|
||||||
|
"localConnectionString": "postgres://openseo:openseo@localhost:5433/openseo",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"r2_buckets": [
|
"r2_buckets": [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user