Skip to content
Open
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
67 changes: 67 additions & 0 deletions alchemy-web/src/content/docs/guides/prisma-postgres.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Prisma Postgres
headline: Manage Prisma Postgres projects and databases with Alchemy.
description: Learn how to configure a Prisma Postgres service token and manage projects, databases, and connection strings with Alchemy.
---

## Prerequisites

- A Prisma Postgres workspace
- A service token with workspace access
- `PRISMA_SERVICE_TOKEN` set in your environment

## Install Dependencies

```bash
bun i alchemy
```

## Configure the Service Token

Create a workspace service token in the Prisma dashboard and export it before running Alchemy commands:

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

## Create a Project and Database

```ts
import alchemy from "alchemy";
import {
Project,
Database,
DatabaseConnection,
} from "alchemy/prisma/postgres";

const app = await alchemy("prisma-postgres-example");

const project = await Project("project", {
name: "prisma-app",
region: "us-east-1",
createDatabase: false,
});

const database = await Database("database", {
project,
name: "primary",
region: "us-east-1",
});
Comment on lines +39 to +49
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to make the region on database optional and just do project.region?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the region selection works is this:

  • projects do not have a concept of region
  • when you create a project and specify createDatabase: true, then you need to specify the region. You will then get a database in that region
  • When you create a database in a project, you need to specify the region

To simplify things, what we could do in Alchemy is hide the ability to automatically create a database with the project. That would eliminate the region and createDatabase flags from Project creation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify things, what we could do in Alchemy is hide the ability to automatically create a database with the project. That would eliminate the region and createDatabase flags from Project creation.

This seems like the right approach. Centralize database config on the Database resource rather than conflate them.


const connection = await DatabaseConnection("connection", {
database,
name: "application",
});

console.log("Database URL", connection.connectionString.unencrypted);

await app.finalize();
```

## Cleanup

To remove all resources created by the guide, run:

```bash
alchemy destroy
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: DatabaseBackups
description: List Prisma Postgres backups and retention information.
---

`DatabaseBackups` fetches the backup catalog for a database along with retention metadata. The resource is read-only and can be combined with `Database` to restore backups.

## List Backups

```ts
import { DatabaseBackups } from "alchemy/prisma/postgres";

const backups = await DatabaseBackups("backups", {
database,
limit: 10,
});

console.log(backups.backups.map((backup) => backup.id));
```

## Use the Most Recent Backup

```ts
import { Database, DatabaseBackups } from "alchemy/prisma/postgres";

const backups = await DatabaseBackups("backups", { database });

if (!backups.mostRecent) {
throw new Error("No backups available");
}

await Database("restored", {
project,
name: "restored",
region: "us-east-1",
fromDatabase: {
database,
backupId: backups.mostRecent.id,
},
});
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: DatabaseConnection
description: Create and rotate Prisma Postgres database connection strings.
---

Use `DatabaseConnection` to issue connection strings for Prisma Postgres databases. Each invocation generates a new API key and connection string.

## Create a Connection

```ts
import { DatabaseConnection } from "alchemy/prisma/postgres";

const connection = await DatabaseConnection("application", {
database,
name: "application",
});
Comment on lines +13 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does database come from. its a bit ambiguous what project is here. Can we give an example with the simplest possible database?


console.log(connection.connectionString.unencrypted);
```

## Rotate If Missing

If the underlying connection is deleted, rerunning the resource automatically creates a replacement with a fresh secret:

```ts
await DatabaseConnection("application", {
database,
name: "application",
});
```
Comment on lines +21 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this would work better as a tip on the create a connection section since you're explaining how the resource works not showing any unique configuration.

CC @JacobMGEvans


## Using the Secret in Environment Variables

```ts
const connection = await DatabaseConnection("application", {
database,
name: "application",
});

authEnv.PRISMA_DATABASE_URL = connection.connectionString;
```
66 changes: 66 additions & 0 deletions alchemy-web/src/content/docs/providers/prisma-postgres/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Database
description: Provision Prisma Postgres databases, including restoring from backups.
---

`Database` creates additional databases within a Prisma Postgres project. Databases inherit workspace access from their parent project and support restoring from backups created by Prisma.

## Create a Database

```ts
import { Database } from "alchemy/prisma/postgres";

const database = await Database("primary", {
project,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does project come from. its a bit ambiguous what project is here. Can we give an example with the simplest possible project?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - thank you!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that Planetscale refers to orgs and databases by id instead of objects. Is that the preferred approach?

const devBranch = await Branch("feature-123", {
  name: "feature-123",
  organizationId: "my-org",
  databaseName: "my-database",
  parentBranch: "main",
  isProduction: false,
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that Planetscale refers to orgs and databases by id instead of objects. Is that the preferred approach?

No, but great question.

The preferred approach is to drop the Id suffix and use a union type. For example: organization: string | Organization.

The planetscale should be:

const devBranch = await Branch("feature-123", {
  name: "feature-123",
  organization: "my-org" | MyOrg,
  database: "my-database" | MyDatabase,
  parentBranch: "main",
  isProduction: false,
});

@Mkassabov we should make these changes in a separate PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenbs
better example is our new clickhouse provider:

const organizationId =
typeof props.organization === "string"
? await OrganizationRef(props.organization)
.then((organization) => organization.id!)
.catch(() => props.organization as string)
: props.organization.id!;

name: "primary",
region: "us-east-1",
});
```

## Disable Adoption

Force a new database instead of reusing an existing one:

```ts
import { Database } from "alchemy/prisma/postgres";

const database = await Database("isolated", {
project,
name: "isolated",
region: "eu-central-1",
adopt: false,
});
Comment on lines +27 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we generally have adopt: false and you have to specify it manually or run with the --adopt flag. can we keep that behavior consistent with the rest of alchemy here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I was unsure what to do, so thanks for calling it out.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside a resource you need to check props.adopt ?? this.scope.adopt.

this.scope.adopt is how we apply blanket adopt across an app or scope.

```

## Restore from Backup

Restore a database from another database and an optional backup id:

```ts
import { Database, DatabaseBackups } from "alchemy/prisma/postgres";

const backups = await DatabaseBackups("backups", { database: sourceDatabase });

const restored = await Database("restore", {
project,
name: "restore",
region: "us-east-1",
fromDatabase: {
database: sourceDatabase,
backupId: backups.mostRecent?.id,
},
});
```

## Promote to Default Database

Set `isDefault` to true to promote the database when the workspace allows it:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes me hesitant since it could be isDefault on multiple databases but only one of them promotes. It dilutes alchemy as being the sense of truth.

could we do something like canPromoteToDefault or at least an output property to show if something is the default?


```ts
const database = await Database("default", {
project,
name: "default",
region: "us-east-1",
isDefault: true,
});
```
61 changes: 61 additions & 0 deletions alchemy-web/src/content/docs/providers/prisma-postgres/project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Project
description: Create or adopt Prisma Postgres projects within a workspace.
---

`Project` provisions Prisma Postgres projects and optionally creates the default database. Service tokens scoped to the workspace must be supplied via `PRISMA_SERVICE_TOKEN` or the `serviceToken` prop.

## Minimal Project

Create a project without provisioning the default database:

```ts
import { Project } from "alchemy/prisma/postgres";

const project = await Project("app-project", {
name: "app-project",
region: "us-east-1",
createDatabase: false,
});
```

## Adopt Existing Project

Reuse a project if it already exists in the workspace by leaving the default `adopt: true` behaviour:

```ts
import { Project } from "alchemy/prisma/postgres";

const project = await Project("existing", {
name: "existing",
region: "us-east-1",
});
```

## Force New Project Creation

Disable adoption to ensure a fresh project is created:

```ts
import { Project } from "alchemy/prisma/postgres";

const project = await Project("fresh", {
name: "fresh-project",
region: "eu-central-1",
createDatabase: false,
adopt: false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adopt should be false by default

});
```

## Custom Service Token

```ts
import { Project } from "alchemy/prisma/postgres";

const project = await Project("project", {
name: "per-region",
region: "ap-southeast-1",
createDatabase: false,
serviceToken: alchemy.secret(process.env.PRISMA_PROJECT_TOKEN),
});
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Workspace
description: Resolve Prisma Postgres workspace metadata for use with other resources.
---

Use the `Workspace` resource to retrieve workspace details with either the workspace id or name. This is useful when you need to display metadata or verify that a service token has access to the expected workspace.

## Lookup by ID

```ts
import { Workspace } from "alchemy/prisma/postgres";

const workspace = await Workspace("workspace", {
id: "wksp_cmg94yrap00a9xgfncx1mwt34",
});
```

## Lookup by Name

```ts
import { Workspace } from "alchemy/prisma/postgres";

const workspace = await Workspace("workspace", {
name: "Production",
});
```

## Custom Service Token

```ts
import { Workspace } from "alchemy/prisma/postgres";

const workspace = await Workspace("workspace", {
id: "wksp_cmg94yrap00a9xgfncx1mwt34",
serviceToken: alchemy.secret(process.env.PRISMA_SERVICE_TOKEN),
});
```
8 changes: 8 additions & 0 deletions alchemy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@
"bun": "./src/planetscale/index.ts",
"import": "./lib/planetscale/index.js"
},
"./prisma": {
"bun": "./src/prisma/index.ts",
"import": "./lib/prisma/index.js"
},
"./prisma/postgres": {
"bun": "./src/prisma/postgres/index.ts",
"import": "./lib/prisma/postgres/index.js"
},
"./random": {
"bun": "./src/random/index.ts",
"import": "./lib/random/index.js"
Expand Down
1 change: 1 addition & 0 deletions alchemy/src/prisma/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./postgres/index.ts";
44 changes: 44 additions & 0 deletions alchemy/src/prisma/postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Prisma Postgres Provider

The Prisma Postgres provider lets you manage Prisma Postgres projects, databases, connection strings, and backup metadata through Alchemy. It interacts with the [Prisma Postgres Management API](https://www.prisma.io/docs/postgres/introduction/management-api) using a workspace service token.

## Authentication

Set the `PRISMA_SERVICE_TOKEN` environment variable or pass `serviceToken` on individual resources. Service tokens are scoped to a Prisma workspace.

## Resources

- [Workspace](./workspace.ts) – look up workspace metadata by id or name
- [Project](./project.ts) – create or adopt Prisma Postgres projects
- [Database](./database.ts) – provision databases and restore from backups
- [DatabaseConnection](./database-connection.ts) – create and manage database connection strings
- [DatabaseBackups](./database-backups.ts) – list available backups and retention metadata

## Usage

```ts
import { Project, Database, DatabaseConnection } from "alchemy/prisma/postgres";

const project = await Project("app-project", {
name: "app-project",
region: "us-east-1",
createDatabase: false,
});

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

const connection = await DatabaseConnection("primary-conn", {
database,
name: "app",
});
```

## Environment Variables

| Variable | Description |
| ----------------------- | -------------------------------------- |
| `PRISMA_SERVICE_TOKEN` | Prisma Postgres workspace service token |
Loading
Loading