Skip to content

Commit

Permalink
wrangler: Hyperdrive dev bindings (#4222)
Browse files Browse the repository at this point in the history
* Add Hyperdrive binding support in wrangler dev

* remove hyperdrive local dev warning

* Add changeset

* Add e2e tests for Hyperdrive

* empty commit to kick off builds
  • Loading branch information
tmthecoder committed Nov 7, 2023
1 parent 33bd75d commit f867e01
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-goats-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Support for hyperdrive bindings in local wrangler dev
94 changes: 93 additions & 1 deletion packages/wrangler/e2e/dev.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import crypto from "node:crypto";
import * as nodeNet from "node:net";
import path from "node:path";
import { setTimeout } from "node:timers/promises";
import getPort from "get-port";
import shellac from "shellac";
import { fetch } from "undici";
import { beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { retry } from "./helpers/retry";
import { dedent, makeRoot, seed } from "./helpers/setup";
import { WRANGLER } from "./helpers/wrangler-command";
Expand Down Expand Up @@ -278,3 +279,94 @@ describe("dev registry", () => {
});
});
});

describe("hyperdrive dev tests", () => {
let worker: DevWorker;
let server: nodeNet.Server;

beforeEach(async () => {
worker = await makeWorker();
server = nodeNet.createServer().listen();
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
}
`,
}));
});

it("matches expected configuration parameters", async () => {
await worker.runDevSession("", async (port) => {
const { text } = await retry(
(s) => {
return s.status !== 200;
},
async () => {
const resp = await fetch(`http://127.0.0.1:${port}`);
return { text: await resp.text(), status: resp.status };
}
);
const url = new URL(text);
expect(url.pathname).toBe("/some_db");
expect(url.username).toBe("user");
expect(url.password).toBe("pass");
expect(url.host).not.toBe("localhost");
});
});

it("connects to a socket", async () => {
const socketMsgPromise = new Promise((resolve, _) => {
server.on("connection", (sock) => {
sock.on("data", (data) => {
expect(new TextDecoder().decode(data)).toBe("test string");
server.close();
resolve({});
});
});
});
await worker.runDevSession("", async (port) => {
await retry(
(s) => {
return s.status !== 200;
},
async () => {
const resp = await fetch(`http://127.0.0.1:${port}/connect`);
return { text: await resp.text(), status: resp.status };
}
);
});
await socketMsgPromise;
});
afterEach(() => {
if (server.listening) {
server.close();
}
});
});
2 changes: 2 additions & 0 deletions packages/wrangler/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ interface EnvironmentNonInheritable {
binding: string;
/** The id of the database. */
id: string;
/** The local database connection string for `wrangler dev` */
localConnectionString?: string;
}[];

/**
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/deployment-bundle/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export interface CfConstellation {
export interface CfHyperdrive {
binding: string;
id: string;
localConnectionString?: string;
}

interface CfService {
Expand Down
9 changes: 8 additions & 1 deletion packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,14 @@ function getBindings(
],
vectorize: configParam.vectorize,
constellation: configParam.constellation,
hyperdrive: configParam.hyperdrive,
hyperdrive: configParam.hyperdrive.map((hyperdrive) => {
if (!hyperdrive.localConnectionString) {
throw new Error(
`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`
);
}
return hyperdrive;
}),
};

return bindings;
Expand Down
6 changes: 0 additions & 6 deletions packages/wrangler/src/dev/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,6 @@ function DevSession(props: DevSessionProps) {
);
}

if (props.local && props.bindings.hyperdrive?.length) {
logger.warn(
"Hyperdrive does not currently support 'wrangler dev' in local mode at this stage of the beta. Use the '--remote' flag to test a Hyperdrive configuration before deploying."
);
}

const announceAndOnReady: typeof props.onReady = (finalIp, finalPort) => {
if (process.send) {
process.send(
Expand Down
7 changes: 7 additions & 0 deletions packages/wrangler/src/dev/miniflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { Config } from "../config";
import type {
CfD1Database,
CfDurableObject,
CfHyperdrive,
CfKvNamespace,
CfQueue,
CfR2Bucket,
Expand Down Expand Up @@ -201,6 +202,9 @@ function d1DatabaseEntry(db: CfD1Database): [string, string] {
function queueProducerEntry(queue: CfQueue): [string, string] {
return [queue.binding, queue.queue_name];
}
function hyperdriveEntry(hyperdrive: CfHyperdrive): [string, string] {
return [hyperdrive.binding, hyperdrive.localConnectionString ?? ""];
}
type QueueConsumer = NonNullable<Config["queues"]["consumers"]>[number];
function queueConsumerEntry(consumer: QueueConsumer) {
const options = {
Expand Down Expand Up @@ -318,6 +322,9 @@ function buildBindingOptions(config: ConfigBundle) {
queueConsumers: Object.fromEntries(
config.queueConsumers?.map(queueConsumerEntry) ?? []
),
hyperdrives: Object.fromEntries(
bindings.hyperdrive?.map(hyperdriveEntry) ?? []
),

durableObjects: Object.fromEntries([
...internalObjects.map(({ name, class_name }) => [name, class_name]),
Expand Down

0 comments on commit f867e01

Please sign in to comment.