diff --git a/packages/alchemy/src/Prisma/Database.ts b/packages/alchemy/src/Prisma/Database.ts index e1b67ec313..3c4307a3ad 100644 --- a/packages/alchemy/src/Prisma/Database.ts +++ b/packages/alchemy/src/Prisma/Database.ts @@ -6,6 +6,7 @@ import { deleteDatabase, getDatabases, getDatabase, + getProject, getProjectBranches, getProjectDatabases, updateDatabase, @@ -136,7 +137,8 @@ export interface DatabaseProps { */ name?: string; /** - * Region for the database. + * Region for the database. `"inherit"` uses the project's default region, + * or the default database's region when the project has none. * * @default "us-east-1" */ @@ -437,12 +439,16 @@ const resolveDatabaseRegion = Effect.fn(function* ( if (region !== "inherit") { return (region ?? "us-east-1") as PrismaRegionId; } + const project = yield* getProject({ id: projectId }); + if (project.data.defaultRegion !== null) { + return project.data.defaultRegion as PrismaRegionId; + } const database = yield* findDefaultDatabase(projectId); const inherited = database?.region?.id; if (inherited === undefined) { return yield* Effect.fail( new Error( - `Cannot resolve Prisma database region 'inherit' because project '${projectId}' has no default database region. Create or promote a default database first, or specify an explicit region.`, + `Cannot resolve Prisma database region 'inherit' because project '${projectId}' has no default region and no default database region. Create or promote a default database first, or specify an explicit region.`, ), ); } diff --git a/packages/alchemy/test/Prisma/Database.test.ts b/packages/alchemy/test/Prisma/Database.test.ts index 980cff2d38..f5cb3d31da 100644 --- a/packages/alchemy/test/Prisma/Database.test.ts +++ b/packages/alchemy/test/Prisma/Database.test.ts @@ -365,3 +365,55 @@ test.provider( }), { tags: logicalIdTags, timeout: 180_000 }, ); + +test.provider( + "region inherit uses the project default region when the project has no default database", + Effect.fn(function* (stack: Test.ScratchStack) { + yield* stack.destroy(); + + const resources = Effect.gen(function* () { + const project = yield* Prisma.Project("Project", { + createDatabase: false, + region: "eu-central-1", + }); + const database = yield* Prisma.Database("Inherited", { project, region: "inherit" }); + return { project, database }; + }); + + const initial = yield* stack.deploy(resources); + expect((yield* getProject({ id: initial.project.projectId })).data.defaultRegion).toBe( + "eu-central-1", + ); + expect(initial.database.region).toBe("eu-central-1"); + expect((yield* observeDatabase(initial.database.databaseId)).region?.id).toBe("eu-central-1"); + + const repeated = yield* stack.deploy(resources); + expect(repeated.database.databaseId).toBe(initial.database.databaseId); + + yield* stack.destroy(); + yield* expectDatabaseGone(initial.database.databaseId); + yield* expectProjectGone(initial.project.projectId); + }), + { tags: logicalIdTags, timeout: 180_000 }, +); + +test.provider( + "region inherit fails before creating anything when the project has no region", + Effect.fn(function* (stack: Test.ScratchStack) { + yield* stack.destroy(); + + const failure = yield* failureOf( + stack.deploy( + Effect.gen(function* () { + const project = yield* Prisma.Project("Project", { createDatabase: false }); + const database = yield* Prisma.Database("Inherited", { project, region: "inherit" }); + return { project, database }; + }), + ), + ); + expect(failure.text).toContain("has no default region and no default database region"); + + yield* stack.destroy(); + }), + { tags: logicalIdTags, timeout: 180_000 }, +); diff --git a/packages/alchemy/test/Prisma/ManagementLifecycle.test.ts b/packages/alchemy/test/Prisma/ManagementLifecycle.test.ts index f2c9509ad0..6a82a56bb4 100644 --- a/packages/alchemy/test/Prisma/ManagementLifecycle.test.ts +++ b/packages/alchemy/test/Prisma/ManagementLifecycle.test.ts @@ -956,11 +956,16 @@ const apiDatabase = ( const makeDatabaseCloud = () => { const databases = new Map(); const calls: Array<[string, unknown?]> = []; + const project = { defaultRegion: null as string | null }; let nextId = 1; // The same in-memory cloud, served over the wire for the Database resource. const fake = makeFakeManagementApi((request) => { const segments = request.pathname.split("/").filter((s) => s.length > 0); + if (segments.length === 3 && segments[1] === "projects" && request.method === "GET") { + return data(toWireProject(apiProject(segments[2]!, "app", project.defaultRegion))); + } + if (request.pathname === "/v1/databases" && request.method === "GET") { return page(Array.from(databases.values()).map(toWireDatabase)); } @@ -1048,7 +1053,7 @@ const makeDatabaseCloud = () => { return unhandled(request); }); - return { fake, calls, databases }; + return { fake, calls, databases, project }; }; const generatedDatabaseRecoveryCloud = makeDatabaseCloud(); @@ -1258,7 +1263,7 @@ const inheritedRegionCloud = makeDatabaseCloud(); const inheritedRegion = Test.make({ providers: databaseLayer(inheritedRegionCloud.fake) }); inheritedRegion.test.provider( - "Database region inherit is stable and follows the project default region", + "Database region inherit falls back to the default database region", (stack) => Effect.gen(function* () { inheritedRegionCloud.databases.clear();