Skip to content

Commit

Permalink
Add feature to use environment variable for Hyperdrive local dev conn…
Browse files Browse the repository at this point in the history
…ection string (#4900)

* hyperdrive: support HYPERDRIVE_LOCAL_CONNECTION_STRING

* hyperdrive: add changeset

* Fix build issues and add tests

* Use binding name in hyperdrive local string environment variable

* Fix changeset, use simpler method for grabbing environment variable

---------

Co-authored-by: Matt Silverlock <matt@eatsleeprepeat.net>
  • Loading branch information
OilyLime and elithrar authored Feb 14, 2024
1 parent 9f78704 commit 3389f2e
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 5 deletions.
7 changes: 7 additions & 0 deletions .changeset/khaki-feet-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

feature: allow hyperdrive users to set local connection string as environment variable

Wrangler dev now supports the HYPERDRIVE_LOCAL_CONNECTION_STRING environmental variable for connecting to a local database instance when testing Hyperdrive in local development. This environmental variable takes precedence over the localConnectionString set in wrangler.toml.
105 changes: 102 additions & 3 deletions packages/wrangler/e2e/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ describe("hyperdrive dev tests", () => {
beforeEach(async () => {
worker = await makeWorker();
server = nodeNet.createServer().listen();
});

it("matches expected configuration parameters", async () => {
let port = 5432;
if (server.address() && typeof server.address() !== "string") {
port = (server.address() as nodeNet.AddressInfo).port;
Expand Down Expand Up @@ -468,9 +471,6 @@ describe("hyperdrive dev tests", () => {
}
`,
}));
});

it("matches expected configuration parameters", async () => {
await worker.runDevSession("", async (session) => {
const { text } = await retry(
(s) => {
Expand All @@ -490,6 +490,39 @@ describe("hyperdrive dev tests", () => {
});

it("connects to a socket", async () => {
let port = 5432;
if (server.address() && typeof server.address() !== "string") {
port = (server.address() as nodeNet.AddressInfo).port;
}
await worker.seed((workerName) => ({
"wrangler.toml": dedent`
name = "${workerName}"
main = "src/index.ts"
compatibility_date = "2023-10-25"
[[hyperdrive]]
binding = "HYPERDRIVE"
id = "hyperdrive_id"
localConnectionString = "postgresql://user:pass@127.0.0.1:${port}/some_db"
`,
"src/index.ts": dedent`
export default {
async fetch(request, env) {
if (request.url.includes("connect")) {
const conn = env.HYPERDRIVE.connect();
await conn.writable.getWriter().write(new TextEncoder().encode("test string"));
}
return new Response(env.HYPERDRIVE?.connectionString ?? "no")
}
}`,
"package.json": dedent`
{
"name": "${workerName}",
"version": "0.0.0",
"private": true
}
`,
}));
const socketMsgPromise = new Promise((resolve, _) => {
server.on("connection", (sock) => {
sock.on("data", (data) => {
Expand All @@ -513,10 +546,76 @@ describe("hyperdrive dev tests", () => {
await socketMsgPromise;
});

it("uses HYPERDRIVE_LOCAL_CONNECTION_STRING for the localConnectionString variable in the binding", async () => {
let port = 5432;
if (server.address() && typeof server.address() !== "string") {
port = (server.address() as nodeNet.AddressInfo).port;
}
await worker.seed((workerName) => ({
"wrangler.toml": dedent`
name = "${workerName}"
main = "src/index.ts"
compatibility_date = "2023-10-25"
[[hyperdrive]]
binding = "HYPERDRIVE"
id = "hyperdrive_id"
`,
"src/index.ts": dedent`
export default {
async fetch(request, env) {
if (request.url.includes("connect")) {
const conn = env.HYPERDRIVE.connect();
await conn.writable.getWriter().write(new TextEncoder().encode("test string"));
}
return new Response(env.HYPERDRIVE?.connectionString ?? "no")
}
}`,
"package.json": dedent`
{
"name": "${workerName}",
"version": "0.0.0",
"private": true
}
`,
}));
const socketMsgPromise = new Promise((resolve, _) => {
server.on("connection", (sock) => {
sock.on("data", (data) => {
expect(new TextDecoder().decode(data)).toBe("test string");
server.close();
resolve({});
});
});
});
// eslint-disable-next-line turbo/no-undeclared-env-vars
process.env.WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE = `postgresql://user:pass@127.0.0.1:${port}/some_db`;
await worker.runDevSession("", async (session) => {
await retry(
(s) => {
return s.status !== 200;
},
async () => {
const resp = await fetch(`http://127.0.0.1:${session.port}/connect`);
return { text: await resp.text(), status: resp.status };
}
);
});
await socketMsgPromise;
});

afterEach(() => {
if (server.listening) {
server.close();
}
if (
// eslint-disable-next-line turbo/no-undeclared-env-vars
process.env.WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE !==
undefined
) {
// eslint-disable-next-line turbo/no-undeclared-env-vars
delete process.env.WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE;
}
});
});

Expand Down
21 changes: 19 additions & 2 deletions packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -955,11 +955,28 @@ export function getBindings(
vectorize: configParam.vectorize,
constellation: configParam.constellation,
hyperdrive: configParam.hyperdrive.map((hyperdrive) => {
if (!hyperdrive.localConnectionString) {
const connectionStringFromEnv =
process.env[
`WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${hyperdrive.binding}`
];
if (!connectionStringFromEnv || !hyperdrive.localConnectionString) {
throw new UserError(
`In development, you should use a local postgres connection string to emulate hyperdrive functionality. Please setup postgres locally and set the value of "${hyperdrive.binding}"'s "localConnectionString" to the postgres connection string in your wrangler.toml`
`When developing locally, you should use a local Postgres connection string to emulate Hyperdrive functionality. Please setup Postgres locally and set the value of the 'WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${hyperdrive.binding}' variable or "${hyperdrive.binding}"'s "localConnectionString" to the Postgres connection string.`
);
}

// If there is a non-empty connection string specified in the environment,
// use that as our local connection stirng configuration.
if (
connectionStringFromEnv !== undefined &&
connectionStringFromEnv !== ""
) {
logger.log(
`Found a non-empty WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING variable for binding. Hyperdrive will connect to this database during local development.`
);
hyperdrive.localConnectionString = connectionStringFromEnv;
}

return hyperdrive;
}),
};
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/environment-variables/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type VariableNames =
| "CLOUDFLARE_API_KEY"
| "CLOUDFLARE_API_TOKEN"
| "CLOUDFLARE_EMAIL"
| `WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${string}`
| "NO_CONSTELLATION_WARNING"
| "NO_HYPERDRIVE_WARNING"
| "WRANGLER_API_ENVIRONMENT"
Expand Down

0 comments on commit 3389f2e

Please sign in to comment.