feat(prisma): consume @distilled.cloud/prisma-postgres instead of the hand-rolled client - #1290
Merged
sam-goodwin merged 13 commits intoSep 16, 2026
Conversation
Pin the distilled submodule at 49ae23cb1 (the prisma-postgres management coverage branch) and add the package as a workspace dependency plus a TypeScript project reference, mirroring how @distilled.cloud/neon is wired. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Credentials.ts resolves the distilled `Credentials` service lazily from the Prisma auth profile, preserving PrismaEnvironment's PRISMA_API_URL override and its validation (the resolved origin becomes `apiBaseUrl`). The service holds an effect, so nothing resolves until the first API call and `alchemy dev` still needs no token. Providers.ts provides it alongside distilled's default retry policy, the way src/Cloudflare/Providers.ts does. Both are merged inside the management-client pipe so the auth layers below satisfy them; the fetch transport is supplied with Layer.provide so it cannot override the ambient HttpClient other providers depend on. The retry envelope changes with this: Retry.makeDefault (8 retries, 250ms base, honors Retry-After) replaces the client's 4x100ms idempotent-only policy, so creates opt out with Retry.none. Credentials.ts is internal wiring: null export-map entry plus the matching PublicSurface expectations. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
force-pushed
the
claude/distilled-effect-clients-refactor-697b6e
branch
6 times, most recently
from
August 21, 2026 13:00
629fcf1 to
4c9f973
Compare
Project.ts calls the generated management ops directly and handles failures
by typed tag (`Effect.catchTag("NotFound"|"Conflict")`) instead of the
raw-status helpers. Creates opt out of the retry policy with distilled's
`Retry.none`, as src/AWS/ApiGateway/common.ts does, because a retry policy
cannot see the request and a replayed create would duplicate the resource.
List operations walk the cursor inline, following src/Neon/Project.ts.
One PrismaClient reference remains, for the delete path that delegates to
destroyProjectApps — D3 migrates that with ComputeLifecycle.
Shared helpers widen to the structural minimum they read so both the
distilled shapes and Types.ts satisfy them (Internal/Observed.ts):
extractConnectionSecrets takes an ObservedConnection and normalizes
possibly-redacted secrets; recoverDatabaseConnectionSecrets is generic over
an ObservedDatabase.
Project's provider tests move from a fake PrismaManagementClient to a fake
HttpClient (test/Prisma/fixtures/FakeManagementApi.ts, lifted from the
harness in test/Prisma/Client.test.ts) serving the same in-memory state, so
they now exercise distilled's schema decoding and status-to-tag matching —
including a real 409 becoming the Conflict tag the create-recovery path
catches.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Database, Connection, Branch and Internal/DatabaseSecrets now call the generated management ops directly and handle failures by typed tag, per the pattern Project.ts froze in D1. Non-idempotent creates and credential rotations opt out of the retry policy with Retry.none; list operations walk the cursor inline. Internal/Observed.ts grows the structural shapes these files share (ObservedBranch, ObservedConnectionRecord, ObservedProjectDatabase) plus narrowDatabaseSource, which recovers the discriminated source union from the Management API's string-typed discriminant by checking fields rather than asserting them. Their hermetic tests move to the HTTP fake: ManagementLifecycle serves the database and branch clouds over the wire alongside the project cloud, and Connection/Resources adapt their existing client-shaped handlers onto the same fixture. Test counts are unchanged; assertions gain route and body pins, and the fixture's unhandled-route arm returns 400 rather than 500 so a missing route fails fast instead of being retried eight times. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…rations
D2 group 2: Bucket, BucketAccessKey, EnvironmentVariable, and
SourceRepository consume @distilled.cloud/prisma-postgres operations
directly, replacing their PrismaClient calls. Cursor pagination is walked
inline at each list call site, every non-idempotent create opts out of the
retry policy with Retry.none, and the once-revealed bucket-key secret is
read through the possibly-redacted boundary helper.
Their suites now serve the Management API over the wire through the
client-shaped dispatch adapters (Bucket.test.ts, ManagementLifecycle.test.ts,
Resources.test.ts), exercising distilled's schema decode and typed-error
matching. Test names are preserved.
CustomDomain stays on PrismaClient for now: its create relies on the
200-vs-201 status distinction of POST /v1/apps/{appId}/domains to refuse
silently adopting an already-registered domain, and distilled's REST
protocol does not surface the success status code. Migrating it needs a
distilled-side decision first.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
D2 group 3 (part 1): App.ts and Deployment.ts consume @distilled.cloud/prisma-postgres operations directly for their own calls (app list/get/create/update, branch lookup, deployment list/create). Cursor pagination is walked inline at each list call site and both non-idempotent creates opt out of the retry policy with Retry.none. Both providers keep resolving PrismaClient for the paths that delegate to still-unmigrated helpers, the same shim Project.ts uses for destroyProjectApps: App.delete -> destroyApp, and Deployment's observe/start/promote/destroy helpers. Those helpers migrate in D3 with Compute and ComputeLifecycle. Internal/DeploymentActions.ts moves to D3 for the same reason: dropping its client parameter changes the runtime context of Compute.ts, which would drag Compute's suites into this commit. App.test.ts, Deployment.test.ts, and the Resources.test.ts multi-resource suites serve the migrated routes over the wire through the client-shaped dispatch adapters; assertions follow wire truth (JSON drops undefined members, query params arrive as strings). Test names are preserved. Tests that stub artifact uploads now provide PrismaUploadClient, since the ambient HttpClient is the management fake. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…lled operations D3 (first unit): Compute.ts, ComputeLifecycle.ts, and the Internal helpers (DeploymentObserve, DeploymentActions, DeploymentIdentity, AppPromotion) consume @distilled.cloud/prisma-postgres operations directly and lose their client parameters; App.ts and Project.ts drop the PrismaClient shims those helpers required. Compute and Deployment each keep one shim for the log tail, whose WebSocket transport is carved out of Client.ts separately. CustomDomain stays fully on the hand-rolled client pending the 200-vs-201 question. Non-idempotent creates opt out of the retry policy with Retry.none; start/stop/promote/rollback keep the default policy, matching the old client's idempotent-POST list. The gitlink moves to distilled 81cd422c7, whose patch round types the 404s these lifecycle paths already handle on GET /v1/apps and GET /v1/environment-variables, so their catches become typed tags. Two raw-status predicates are replaced with their typed equivalents: isProjectDeleteBlocked becomes catchTags on Conflict and BadRequest, and isAppProvisioningNotFound retries on the NotFound tag. Where a helper's error union mixes typed API errors with plain Error and TypeScript reduces it to Error, the tag is matched with Predicate.isTagged — instanceof is also unreliable across the workspace's two module instantiations of the distilled error classes. Suites serve the migrated routes over the wire through client-shaped dispatch adapters; Compute.test.ts routes non-API URLs (uploads, health probes) to each test's own transport beneath the management fake. Injected 5xx fixtures become 400s so the retry policy does not replay them, and identity assertions on injected errors become tag/message assertions, since errors are re-decoded at the wire. The stopped-deployment delete diagnostic keeps its own coverage through an unmapped-status fixture with retries disabled. Test names are preserved. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
D3 (second unit): Internal/LogsClient.ts is the hand-written home for the Management API's log streams — the deployment WebSocket and the build NDJSON request builders — consuming PrismaEnvironment the way the hand-rolled client did, with the same path validation and URL building. They stay in alchemy: neither stream fits distilled's request/response operations (the question of a distilled home is deferred to the PR). tailDeploymentLogs loses its client parameter and resolves the environment on the caller's fiber before the callback stream starts, because the reconnect loop forks with Effect.runFork, which cannot carry context. The Compute and Deployment providers drop their last PrismaClient shims; the Operations helpers re-point to the new module, so the delegation test now excludes the two builders from the client-call inventory. The builder assertions move with the code: LogsClient.test.ts pins the wss swap, the from_start mapping, the NDJSON Accept header, and the invalid-path refusal against the new module, so deleting Client.ts cannot silently drop them. The tail suites provide PrismaEnvironment — the test WebSocket server as an http:// API base, so the protocol swap is exercised — and pin the requested path, instead of a client-shaped request fake. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…fake Eight suites carried near-verbatim copies of the adapter that maps their client-shaped handlers onto the wire, and the copies had drifted: one grew a fourth `asResponse` variant, and every DELETE arm called its handler directly instead of going through the missing-handler check, so an unstubbed delete route threw a raw TypeError instead of the diagnosable `unhandled` 400. `runHandler`, one `asResponse`/`voidResponse` pair, and a `dispatchTo(request)` that binds `call`/`callVoid`/`list` now live in `fixtures/FakeManagementApi.ts`. Every verb routes through it. Each suite keeps its own route table. ManagementLifecycle also loses its two dead client fakes (the in-memory clouds serve the wire directly now) and the five `Layer.succeed(PrismaClient, …)` provides for resources that no longer resolve the client. No test names change. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
force-pushed
the
claude/distilled-effect-clients-refactor-697b6e
branch
from
August 21, 2026 14:39
4c9f973 to
93ab75a
Compare
sam-goodwin
marked this pull request as ready for review
September 16, 2026 22:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Use
@distilled.cloud/prisma-postgresfor Prisma resource lifecycle calls instead of the hand-written Management API client./v1/servicesAPI to Alchemy's existingAppandappIdsurface.Includes merged distilled#610, pinned at
2a62c51750623f0466c7b34b415fe489fb57d9e3. Standalone Management API helpers and CustomDomain retain their existing client implementation.