-
Notifications
You must be signed in to change notification settings - Fork 76
feat(prisma): prisma postgres resources #1053
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it possible to make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way the region selection works is this:
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this would work better as a |
||
|
||
## Using the Secret in Environment Variables | ||
|
||
```ts | ||
const connection = await DatabaseConnection("application", { | ||
database, | ||
name: "application", | ||
}); | ||
|
||
authEnv.PRISMA_DATABASE_URL = connection.connectionString; | ||
``` |
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, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch - thank you! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, but great question. The preferred approach is to drop the 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sorenbs alchemy/alchemy/src/clickhouse/service.ts Lines 390 to 395 in 80ef082
|
||||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inside a resource you need to check
|
||||||||||||||
``` | ||||||||||||||
|
||||||||||||||
## 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: | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this makes me hesitant since it could be could we do something like |
||||||||||||||
|
||||||||||||||
```ts | ||||||||||||||
const database = await Database("default", { | ||||||||||||||
project, | ||||||||||||||
name: "default", | ||||||||||||||
region: "us-east-1", | ||||||||||||||
isDefault: true, | ||||||||||||||
}); | ||||||||||||||
``` |
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
}); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./postgres/index.ts"; |
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 | |
Uh oh!
There was an error while loading. Please reload this page.