1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as FetchHttpClient from "effect/http/FetchHttpClient";
5import { AuthProviders } from "../Auth/AuthProvider.ts";
6import { CredentialsStoreLive } from "../Auth/Credentials.ts";
7import { ProfileStore, ProfileStoreLive } from "../Auth/Profile.ts";
8import * as Provider from "../Provider.ts";
9import * as Command from "../Command/index.ts";
10import { PlatformServices } from "../Util/PlatformServices.ts";
11import { proxyChain } from "../Util/proxy-chain.ts";
12import { Server, ServerProvider } from "../Website/Server.ts";
13import {
14 WebsiteArtifact,
15 WebsiteArtifactProvider,
16} from "./Website/Artifact.ts";
17import { PrismaAuth } from "./AuthProvider.ts";
18import { App, AppProvider } from "./App.ts";
19import { Branch, BranchProvider } from "./Branch.ts";
20import { Bucket, BucketProvider } from "./Bucket.ts";
21import { BucketAccessKey, BucketAccessKeyProvider } from "./BucketAccessKey.ts";
22import {
23 PrismaClient,
24 PrismaClientLive,
25 type PrismaManagementClient,
26} from "./Client.ts";
27import { Connection, ConnectionProvider } from "./Connection.ts";
28import { Retry } from "@distilled.cloud/prisma";
29import * as Credentials from "./Credentials.ts";
30import { Compute, ComputeProvider } from "./Compute.ts";
31import { CustomDomain, CustomDomainProvider } from "./CustomDomain.ts";
32import { Database, DatabaseProvider } from "./Database.ts";
33import { Deployment, DeploymentProvider } from "./Deployment.ts";
34import {
35 EnvironmentVariable,
36 EnvironmentVariableProvider,
37} from "./EnvironmentVariable.ts";
38import {
39 PrismaHttpClientLive,
40 PrismaUploadClientLive,
41} from "./Internal/HttpClient.ts";
42import { fromProfile } from "./PrismaEnvironment.ts";
43import { Contract, ContractProvider } from "./ORM/Contract.ts";
44import { Migrate, MigrateProvider } from "./ORM/Migrate.ts";
45import { Project, ProjectProvider } from "./Project.ts";
46import {
47 SourceRepository,
48 SourceRepositoryProvider,
49} from "./SourceRepository.ts";
50
51export { PrismaEnvironment } from "./PrismaEnvironment.ts";
52
53export class Providers extends Provider.ProviderCollection<Providers>()(
54 "Prisma",
55) {}
56
57export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
58
59/**
60 * Standalone operation helpers own a private auth registry because they run
61 * outside a Stack. Credential resolution stays eager here so constructing
62 * `managementApi()` preserves its existing fail-fast behavior — which also
63 * lets the distilled `Credentials` service read the already-resolved
64 * {@link PrismaEnvironment} rather than building its own.
65 */
66const standaloneManagementApiLayer = () =>
67 PrismaClientLive.pipe(
68 Layer.provideMerge(
69 Layer.mergeAll(
70 Credentials.fromEnvironment(),
71 Layer.succeed(Retry.Retry, Retry.makeDefault),
72 ),
73 ),
74 Layer.provideMerge(fromProfile()),
75 Layer.provideMerge(PrismaAuth),
76 Layer.provideMerge(
77 Layer.mergeAll(
78 Layer.succeed(AuthProviders, {}),
79 // The Prisma-scoped upload client (node transport) rides the
80 // providers' output so artifact uploads can reach it at op time.
81 PrismaUploadClientLive,
82 Layer.provide(ProfileStoreLive, PlatformServices),
83 Layer.provide(CredentialsStoreLive, PlatformServices),
84 ),
85 ),
86 // Provide (NOT provideMerge) the node transport privately: it must serve
87 // only the Prisma management client. Exposing it from `providers()` would
88 // override the ambient HttpClient for every other provider in the stack —
89 // Cloudflare Worker script uploads then go out via node:http, which
90 // streams multipart bodies as `Transfer-Encoding: chunked`, and
91 // api.cloudflare.com never answers chunked uploads.
92 Layer.provide(PrismaHttpClientLive),
93 );
94
95/**
96 * Stack provider discovery must register auth without requiring credentials.
97 * The management client is resolved on its first API operation, after
98 * `alchemy profile edit` has had a chance to configure the registered Prisma
99 * auth provider. The nested client layer shares the provider layer's lifetime.
100 */
101const stackManagementApiLayer = () =>
102 Layer.effect(
103 PrismaClient,
104 Effect.gen(function* () {
105 const scope = yield* Effect.scope;
106 const authProviders = yield* AuthProviders;
107 const profileStore = yield* ProfileStore;
108 const client = Layer.buildWithScope(
109 PrismaClientLive.pipe(
110 Layer.provideMerge(
111 fromProfile().pipe(
112 Layer.provide(
113 Layer.mergeAll(
114 Layer.succeed(AuthProviders, authProviders),
115 Layer.succeed(ProfileStore, profileStore),
116 ),
117 ),
118 ),
119 ),
120 Layer.provide(PrismaHttpClientLive),
121 ),
122 scope,
123 ).pipe(Effect.map((context) => Context.get(context, PrismaClient)));
124 const cached = yield* Effect.cached(client);
125 return proxyChain(cached) as PrismaManagementClient;
126 }),
127 ).pipe(
128 Layer.provideMerge(
129 Layer.mergeAll(
130 Credentials.fromAuthProvider(),
131 Layer.succeed(Retry.Retry, Retry.makeDefault),
132 ),
133 ),
134 Layer.provideMerge(PrismaAuth),
135 Layer.provideMerge(
136 Layer.mergeAll(
137 // The Prisma-scoped upload client (node transport) rides the
138 // providers' output so artifact uploads can reach it at op time.
139 PrismaUploadClientLive,
140 Layer.provide(ProfileStoreLive, PlatformServices),
141 Layer.provide(CredentialsStoreLive, PlatformServices),
142 ),
143 ),
144 );
145
146/**
147 * Build a layer for Prisma Management API operation helpers.
148 *
149 * Use this when calling helpers like `Prisma.listProjects()` outside an
150 * Alchemy stack or test. Inside a stack (or a `test.provider` body) deployed
151 * with `Prisma.providers()`, the management client is already in context, so
152 * operation helpers work without providing this layer.
153 *
154 * @example
155 * ```typescript
156 * const projects = yield* Prisma.listProjects().pipe(
157 * Effect.provide(Prisma.managementApi()),
158 * );
159 * ```
160 *
161 * This layer covers the operation helpers. The lifecycle helpers
162 * (`destroyApp`, `destroyDeployment`, `destroyProjectApps`,
163 * `waitForDeploymentStatus`, `syncComputeEnvironment`) also need an
164 * `HttpClient` from the caller, because the Prisma-scoped node transport is
165 * `Layer.provide`d privately here — see the comment on the private provide
166 * above for why it must not reach the surrounding stack. Add one, e.g.:
167 *
168 * ```typescript
169 * yield* Prisma.destroyApp(appId).pipe(
170 * Effect.provide(Prisma.managementApi()),
171 * Effect.provide(FetchHttpClient.layer),
172 * );
173 * ```
174 */
175export const managementApi = () =>
176 standaloneManagementApiLayer().pipe(
177 Layer.provide(FetchHttpClient.layer),
178 Layer.orDie,
179 );
180
181/**
182 * Build a layer that registers all Prisma resource providers, the Prisma
183 * auth provider, resolved credentials, and an HTTP client.
184 *
185 * Every resource is registered with {@link ../Local/ProviderLayer.ts
186 * ProviderLayer.dual}: a **live** implementation backed by the Prisma
187 * Management API and a **local** implementation used by `alchemy dev`
188 * (fabricated `dev:` identifiers; `Prisma.Database` boots a local
189 * `@prisma/dev` server). The engine picks the variant per run and per
190 * resource:
191 *
192 * - `alchemy deploy` resolves the live variant; `alchemy dev` resolves the
193 * local variant automatically.
194 * - Wrap a resource in `Alchemy.remote(...)` to keep it live even during
195 * `alchemy dev`.
196 * - To replace implementations wholesale, construct your own layer instead
197 * of this one: register the {@link Providers} collection and provide your
198 * own per-resource provider layers (compose the exported per-resource
199 * factories like {@link ProjectProvider} with your replacements, and
200 * {@link managementApi} when live operations are needed).
201 *
202 * @example
203 * ```typescript
204 * import * as Alchemy from "alchemy";
205 * import * as Prisma from "alchemy/Prisma";
206 * import * as Effect from "effect/Effect";
207 *
208 * export default Alchemy.Stack(
209 * "MyStack",
210 * { providers: Prisma.providers(), state: Alchemy.localState() },
211 * Effect.gen(function* () {
212 * const project = yield* Prisma.Project("app", {
213 * name: "app",
214 * region: "us-east-1",
215 * });
216 * return { projectId: project.projectId };
217 * }),
218 * );
219 * ```
220 */
221export const providers = () =>
222 Layer.effect(
223 Providers,
224 Provider.collection([
225 Project,
226 Database,
227 Connection,
228 Branch,
229 Bucket,
230 BucketAccessKey,
231 Compute,
232 App,
233 Deployment,
234 CustomDomain,
235 EnvironmentVariable,
236 SourceRepository,
237 Contract,
238 Migrate,
239 Server,
240 WebsiteArtifact,
241 ]),
242 ).pipe(
243 Layer.provideMerge(
244 Layer.mergeAll(
245 ProjectProvider(),
246 DatabaseProvider(),
247 ConnectionProvider(),
248 BranchProvider(),
249 BucketProvider(),
250 BucketAccessKeyProvider(),
251 ComputeProvider(),
252 AppProvider(),
253 DeploymentProvider(),
254 CustomDomainProvider(),
255 EnvironmentVariableProvider(),
256 SourceRepositoryProvider(),
257 ContractProvider(),
258 MigrateProvider(),
259 ServerProvider(),
260 WebsiteArtifactProvider(),
261 ),
262 ),
263 // The management client layer is shared by every live variant. It is
264 // built in both modes but stays inert until the first API operation:
265 // auth registers without resolving credentials, so `alchemy dev` never
266 // needs a Prisma token.
267 Layer.provideMerge(stackManagementApiLayer()),
268 Layer.provideMerge(Command.providers()),
269 Layer.provide(FetchHttpClient.layer),
270 Layer.orDie,
271 );