Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
17 changes: 17 additions & 0 deletions alchemy-web/src/content/docs/providers/cloudflare/hyperdrive.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ const db = await Hyperdrive("my-postgres-db", {
});
```

## Local Development Only

For local-first development when you don't have production credentials yet, you can omit the `origin` property and only provide `dev.origin`. This matches the wrangler.jsonc pattern where you can use a local connection string without needing a real Hyperdrive ID.

```ts
const db = await Hyperdrive("my-postgres-db", {
name: "my-postgres-db",
dev: {
origin: "postgres://postgres:postgres@localhost:5432/postgres",
},
});
```

:::caution
When you're ready to deploy to production, you'll need to add the `origin` property with your production database connection. Alchemy will throw a helpful error if you try to deploy without it.
:::

## With Explicit Origin Object

If you'd prefer to set parameters explicitly, you can use an object.
Expand Down
26 changes: 23 additions & 3 deletions alchemy/src/cloudflare/hyperdrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,11 @@ export interface HyperdriveProps extends CloudflareApiOptions {

/**
* Database connection origin configuration
*
* Optional in local mode - if not provided, dev.origin will be used.
* Required for production deployments.
*/
origin: HyperdriveOriginInput;
origin?: HyperdriveOriginInput;

/**
* Caching configuration
Expand Down Expand Up @@ -311,13 +314,30 @@ export async function Hyperdrive(
id: string,
props: HyperdriveProps,
): Promise<Hyperdrive> {
const origin = normalizeHyperdriveOrigin(props.origin);
// In local mode, origin can be omitted if dev.origin is provided
const devOrigin = props.dev?.origin;
const productionOrigin = props.origin;

if (!Scope.current.local && !productionOrigin) {
throw new Error(
`Hyperdrive "${id}" requires 'origin' for production deployment. ` +
`Add the production database connection to enable deployment.\n\n` +
`For local development only, you can omit 'origin' and only provide 'dev.origin'.`,
);
}

// Use dev.origin as fallback if origin is not provided (local mode only)
const origin = productionOrigin
? normalizeHyperdriveOrigin(productionOrigin)
: normalizeHyperdriveOrigin(devOrigin!);

const dev = {
origin: toConnectionString(
normalizeHyperdriveOrigin(props.dev?.origin ?? origin),
normalizeHyperdriveOrigin(devOrigin ?? productionOrigin!),
),
force: Scope.current.local,
};

return await _Hyperdrive(id, {
...props,
origin,
Expand Down
19 changes: 19 additions & 0 deletions alchemy/test/cloudflare/hyperdrive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ describe.concurrent("Hyperdrive Resource", () => {
}
});

test("hyperdrive with no production origin throws", async (scope) => {
try {
const project = await NeonProject(`${testId}-dev-only`, {
name: `Hyperdrive Test Dev Only ${BRANCH_PREFIX}`,
});

await expect(
Hyperdrive(`${testId}-dev`, {
name: `test-hyperdrive-dev-${BRANCH_PREFIX}`,
dev: {
origin: project.connection_uris[0].connection_parameters,
},
}),
).rejects.toThrowError(/requires 'origin' for production deployment/);
} finally {
await destroy(scope);
}
});

describe("normalizeHyperdriveOrigin", () => {
it("normalizes postgres string origin", () => {
const origin = normalizeHyperdriveOrigin(
Expand Down
4 changes: 4 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions examples/cloudflare-dev-only-hyperdrive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Prisma Postgres Example

This example provisions a Prisma Postgres project, database, and connection string using Alchemy.

## Prerequisites

1. Create a Prisma Postgres workspace service token.
2. Export the token before running the example:

```bash
export PRISMA_SERVICE_TOKEN="sk_..."
```

3. Choose an Alchemy state password and export it (used to encrypt secrets locally):

```bash
export ALCHEMY_PASSWORD="dev-password"
```

## Usage

```bash
bun i
ALCHEMY_PASSWORD=${ALCHEMY_PASSWORD:-dev-password} bun run alchemy.run.ts
```

The script prints the generated database connection string to stdout.

To tear down the resources:

```bash
bun run destroy
```
23 changes: 23 additions & 0 deletions examples/cloudflare-dev-only-hyperdrive/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import alchemy from "alchemy";
import { Hyperdrive, Worker } from "alchemy/cloudflare";
import { Connection, Database, Project } from "alchemy/prisma-postgres";

const app = await alchemy("alchemy-dev-only-hyperdrive");

const project = await Project("project");

const database = await Database("database", {
project,
region: "us-east-1",
});

const connection = await Connection("connection", { database });

const db = await Hyperdrive("dev-only-hyperdrive", {
origin: app.local ? undefined : connection.connectionString.unencrypted,
dev: {
origin: connection.connectionString.unencrypted,
},
});

await app.finalize();
18 changes: 18 additions & 0 deletions examples/cloudflare-dev-only-hyperdrive/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "cloudflare-dev-only-hyperdrive",
"version": "0.0.0",
"description": "Alchemy Cloudflare Dev Only Hyperdrive Example",
"type": "module",
"scripts": {
"build": "tsc -b",
"deploy": "alchemy deploy --env-file ../../.env",
"destroy": "alchemy destroy --env-file ../../.env"
},
"devDependencies": {
"alchemy": "workspace:*",
"typescript": "catalog:"
},
"dependencies": {
"pg": "^8.16.3"
}
}
28 changes: 28 additions & 0 deletions examples/cloudflare-dev-only-hyperdrive/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Client } from "pg";
import type { worker } from "../alchemy.run.ts";

export default {
async fetch(_request: Request, env: typeof worker.Env): Promise<Response> {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString,
});

try {
// Connect to the database
await client.connect();
console.log("Connected to PostgreSQL database");

// Perform a simple query
const result = await client.query("SELECT * FROM pg_tables");

return Response.json({
success: true,
result: result.rows,
});
} catch (error: any) {
console.error("Database error:", error.message);

return new Response("Internal error occurred", { status: 500 });
}
},
};
8 changes: 8 additions & 0 deletions examples/cloudflare-dev-only-hyperdrive/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "."
},
"include": ["./alchemy.run.ts"]
}
Loading