Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oilylime/hyperdrive support #1

Closed
wants to merge 7 commits into from
Closed
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
5 changes: 5 additions & 0 deletions .changeset/happy-keys-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Adds support for Hyperdrive, via `wrangler hyperdrive`.
270 changes: 270 additions & 0 deletions packages/wrangler/src/__tests__/hyperdrive.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import { rest } from "msw";
import { endEventLoop } from "./helpers/end-event-loop";
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
import { mockConsoleMethods } from "./helpers/mock-console";
import { clearDialogs } from "./helpers/mock-dialogs";
import { useMockIsTTY } from "./helpers/mock-istty";
import { createFetchResult, msw } from "./helpers/msw";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";

describe("hyperdrive help", () => {
const std = mockConsoleMethods();
runInTempDir();

it("should show help text when no arguments are passed", async () => {
await runWrangler("hyperdrive");
await endEventLoop();

expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.out).toMatchInlineSnapshot(`
"wrangler hyperdrive

🚀 Configure Hyperdrive databases

Commands:
wrangler hyperdrive create <name> Create a Hyperdrive config
wrangler hyperdrive delete <id> Delete a Hyperdrive config
wrangler hyperdrive get <id> Get a Hyperdrive config
wrangler hyperdrive list List Hyperdrive configs
wrangler hyperdrive update <id> Update a Hyperdrive config

Flags:
-j, --experimental-json-config Experimental: Support wrangler.json [boolean]
-c, --config Path to .toml configuration file [string]
-e, --env Environment to use for operations and .env files [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]

--------------------
📣 Hyperdrive is currently in open beta
📣 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose
📣 To give feedback, visit https://discord.gg/cloudflaredev
--------------------"
`);
});

it("should show help when an invalid argument is pased", async () => {
await expect(() => runWrangler("hyperdrive qwer")).rejects.toThrow(
"Unknown argument: qwer"
);

expect(std.err).toMatchInlineSnapshot(`
"X [ERROR] Unknown argument: qwer

"
`);
expect(std.out).toMatchInlineSnapshot(`
"
wrangler hyperdrive

🚀 Configure Hyperdrive databases

Commands:
wrangler hyperdrive create <name> Create a Hyperdrive config
wrangler hyperdrive delete <id> Delete a Hyperdrive config
wrangler hyperdrive get <id> Get a Hyperdrive config
wrangler hyperdrive list List Hyperdrive configs
wrangler hyperdrive update <id> Update a Hyperdrive config

Flags:
-j, --experimental-json-config Experimental: Support wrangler.json [boolean]
-c, --config Path to .toml configuration file [string]
-e, --env Environment to use for operations and .env files [string]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]

--------------------
📣 Hyperdrive is currently in open beta
📣 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose
📣 To give feedback, visit https://discord.gg/cloudflaredev
--------------------"
`);
});
});

describe("hyperdrive commands", () => {
mockAccountId();
mockApiToken();
runInTempDir();
const { setIsTTY } = useMockIsTTY();

const std = mockConsoleMethods();

beforeEach(() => {
// @ts-expect-error we're using a very simple setTimeout mock here
jest.spyOn(global, "setTimeout").mockImplementation((fn, _period) => {
setImmediate(fn);
});
setIsTTY(true);
});

afterEach(() => {
clearDialogs();
});

it("should handle creating a hyperdrive config", async () => {
mockHyperdriveRequest();
await runWrangler(
"hyperdrive create test123 --connection-string='postgresql://test:password@foo.us-east-2.aws.neon.tech:5432/neondb'"
);
expect(std.out).toMatchInlineSnapshot(`
"🚧 Creating 'test123'
✅ Created new Hyperdrive config
{
\\"id\\": \\"7a040c1eee714e91a30ea6707a2d125c\\",
\\"name\\": \\"test123\\",
\\"origin\\": {
\\"host\\": \\"foo.us-east-2.aws.neon.tech\\",
\\"port\\": 5432,
\\"database\\": \\"neondb\\",
\\"user\\": \\"test\\"
},
\\"caching\\": {
\\"disabled\\": false
}
}"
`);
});

it("should handle listing configs", async () => {
mockHyperdriveRequest();
await runWrangler("hyperdrive list");
expect(std.out).toMatchInlineSnapshot(`
"📋 Listing Hyperdrive configs
┌──────────────────────────────────┬─────────┬────────┬─────────────────────────────┬──────┬──────────┐
│ id │ name │ user │ host │ port │ database │
├──────────────────────────────────┼─────────┼────────┼─────────────────────────────┼──────┼──────────┤
│ fb94f15a95ce4afa803bb21794b2802c │ new-db │ dbuser │ database.server.com │ 3211 │ mydb │
├──────────────────────────────────┼─────────┼────────┼─────────────────────────────┼──────┼──────────┤
│ 7a040c1eee714e91a30ea6707a2d125c │ test123 │ test │ foo.us-east-2.aws.neon.tech │ 5432 │ neondb │
└──────────────────────────────────┴─────────┴────────┴─────────────────────────────┴──────┴──────────┘"
`);
});

it("should handle displaying a config", async () => {
mockHyperdriveRequest();
await runWrangler("hyperdrive get 7a040c1eee714e91a30ea6707a2d125c");
expect(std.out).toMatchInlineSnapshot(`
"{
\\"id\\": \\"7a040c1eee714e91a30ea6707a2d125c\\",
\\"name\\": \\"test123\\",
\\"origin\\": {
\\"host\\": \\"foo.us-east-2.aws.neon.tech\\",
\\"port\\": 5432,
\\"database\\": \\"neondb\\",
\\"user\\": \\"test\\"
},
\\"caching\\": {
\\"disabled\\": false
}
}"
`);
});

it("should handle deleting a config", async () => {
mockHyperdriveRequest();
await runWrangler("hyperdrive delete 7a040c1eee714e91a30ea6707a2d125c");
expect(std.out).toMatchInlineSnapshot(`
"🗑️ Deleting Hyperdrive database config 7a040c1eee714e91a30ea6707a2d125c
✅ Deleted"
`);
});
});

/** Create a mock handler for Hyperdrive API */
function mockHyperdriveRequest() {
msw.use(
rest.get(
"*/accounts/:accountId/hyperdrive/configs/7a040c1eee714e91a30ea6707a2d125c",
(req, res, ctx) => {
return res.once(
ctx.json(
createFetchResult(
{
id: "7a040c1eee714e91a30ea6707a2d125c",
name: "test123",
origin: {
host: "foo.us-east-2.aws.neon.tech",
port: 5432,
database: "neondb",
user: "test",
},
caching: {
disabled: false,
},
},

true
)
)
);
}
),
rest.post("*/accounts/:accountId/hyperdrive/configs", (req, res, ctx) => {
return res.once(
ctx.json(
createFetchResult(
{
id: "7a040c1eee714e91a30ea6707a2d125c",
name: "test123",
origin: {
host: "foo.us-east-2.aws.neon.tech",
port: 5432,
database: "neondb",
user: "test",
},
caching: {
disabled: false,
},
},
true
)
)
);
}),
rest.delete(
"*/accounts/:accountId/hyperdrive/configs/7a040c1eee714e91a30ea6707a2d125c",
(req, res, ctx) => {
return res.once(ctx.json(createFetchResult(null, true)));
}
),
rest.get("*/accounts/:accountId/hyperdrive/configs", (req, res, ctx) => {
return res.once(
ctx.json(
createFetchResult(
[
{
id: "fb94f15a95ce4afa803bb21794b2802c",
name: "new-db",
origin: {
host: "database.server.com",
port: 3211,
database: "mydb",
user: "dbuser",
},
caching: {
disabled: false,
},
},
{
id: "7a040c1eee714e91a30ea6707a2d125c",
name: "test123",
origin: {
host: "foo.us-east-2.aws.neon.tech",
port: 5432,
database: "neondb",
user: "test",
},
caching: {
disabled: false,
},
},
],
true
)
)
);
})
);
}
2 changes: 2 additions & 0 deletions packages/wrangler/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe("wrangler", () => {
wrangler r2 📦 Interact with an R2 store
wrangler dispatch-namespace 📦 Interact with a dispatch namespace
wrangler d1 🗄 Interact with a D1 database
wrangler hyperdrive 🚀 Configure Hyperdrive databases
wrangler constellation 🤖 Interact with Constellation models
wrangler pubsub 📮 Interact and manage Pub/Sub Brokers
wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections
Expand Down Expand Up @@ -101,6 +102,7 @@ describe("wrangler", () => {
wrangler r2 📦 Interact with an R2 store
wrangler dispatch-namespace 📦 Interact with a dispatch namespace
wrangler d1 🗄 Interact with a D1 database
wrangler hyperdrive 🚀 Configure Hyperdrive databases
wrangler constellation 🤖 Interact with Constellation models
wrangler pubsub 📮 Interact and manage Pub/Sub Brokers
wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections
Expand Down
76 changes: 76 additions & 0 deletions packages/wrangler/src/hyperdrive/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { fetchResult } from "../cfetch";
import { requireAuth } from "../user";
import type { Config } from "../config";

export type HyperdriveConfig = {
id: string;
name: string;
origin: PublicOrigin;
};

export type Origin = {
host?: string;
port?: number;
};

export type PublicOrigin = Origin & {
scheme?: string;
database?: string;
user?: string;
};

export type OriginWithPassword = PublicOrigin & {
password?: string;
};

export type CreateUpdateHyperdriveBody = {
name?: string;
origin: OriginWithPassword;
};

export async function createConfig(
config: Config,
body: CreateUpdateHyperdriveBody
): Promise<HyperdriveConfig> {
const accountId = await requireAuth(config);
return await fetchResult(`/accounts/${accountId}/hyperdrive/configs`, {
method: "POST",
body: JSON.stringify(body),
});
}

export async function deleteConfig(config: Config, id: string): Promise<void> {
const accountId = await requireAuth(config);
return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, {
method: "DELETE",
});
}

export async function getConfig(
config: Config,
id: string
): Promise<HyperdriveConfig> {
const accountId = await requireAuth(config);
return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, {
method: "GET",
});
}

export async function listConfigs(config: Config): Promise<HyperdriveConfig[]> {
const accountId = await requireAuth(config);
return await fetchResult(`/accounts/${accountId}/hyperdrive/configs`, {
method: "GET",
});
}

export async function updateConfig(
config: Config,
id: string,
body: CreateUpdateHyperdriveBody
): Promise<HyperdriveConfig> {
const accountId = await requireAuth(config);
return await fetchResult(`/accounts/${accountId}/hyperdrive/configs/${id}`, {
method: "PUT",
body: JSON.stringify(body),
});
}
2 changes: 2 additions & 0 deletions packages/wrangler/src/hyperdrive/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const hyperdriveBetaWarning =
"--------------------\n📣 Hyperdrive is currently in open beta\n📣 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose\n📣 To give feedback, visit https://discord.gg/cloudflaredev\n--------------------\n";
Loading
Loading