| 1 | import * as Effect from "effect/Effect"; |
| 2 | import { |
| 3 | type GetServiceDeploymentsResponse, |
| 4 | getServiceDeployments, |
| 5 | } from "@distilled.cloud/prisma/management"; |
| 6 | import { PrismaPaginationError } from "./Pagination.ts"; |
| 7 | |
| 8 | /** Prove that a deployment belongs to an App before mutating or deleting it. */ |
| 9 | export const ensureDeploymentMembership = Effect.fn(function* ( |
| 10 | appId: string, |
| 11 | deployment: { id: string; foundryVersionId: string }, |
| 12 | knownLatestDeploymentId?: string | null, |
| 13 | ) { |
| 14 | if (knownLatestDeploymentId === deployment.id) return; |
| 15 | // Distilled emits the cursor-paginated list operations as plain ops, so |
| 16 | // callers walk `pagination` themselves (see `src/Neon/Project.ts`). |
| 17 | const deployments: GetServiceDeploymentsResponse["data"][number][] = []; |
| 18 | let cursor: string | undefined; |
| 19 | while (true) { |
| 20 | const page = yield* getServiceDeployments( |
| 21 | cursor === undefined |
| 22 | ? { serviceId: appId } |
| 23 | : { serviceId: appId, cursor }, |
| 24 | ); |
| 25 | deployments.push(...page.data); |
| 26 | const nextCursor = page.pagination.nextCursor; |
| 27 | if (!page.pagination.hasMore) break; |
| 28 | if (nextCursor === null) { |
| 29 | return yield* Effect.fail( |
| 30 | new PrismaPaginationError({ |
| 31 | message: |
| 32 | "Invalid Prisma Management API pagination response from getServiceDeployments: hasMore was true without a non-empty nextCursor", |
| 33 | }), |
| 34 | ); |
| 35 | } |
| 36 | cursor = nextCursor; |
| 37 | } |
| 38 | const matches = deployments.filter( |
| 39 | (candidate) => candidate.id === deployment.id, |
| 40 | ); |
| 41 | if ( |
| 42 | matches.length !== 1 || |
| 43 | matches[0]?.foundryVersionId !== deployment.foundryVersionId |
| 44 | ) { |
| 45 | return yield* Effect.fail( |
| 46 | new Error( |
| 47 | `Prisma deployment '${deployment.id}' with Foundry version '${deployment.foundryVersionId}' is not uniquely owned by App '${appId}'. Refusing to start, promote, or delete a mismatched deployment.`, |
| 48 | ), |
| 49 | ); |
| 50 | } |
| 51 | }); |