1import * as Config from "effect/Config";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import { resolveProviderConfig } from "../Auth/Resolve.ts";
6import {
7 PRISMA_AUTH_PROVIDER_NAME,
8 type PrismaAuthConfig,
9 type PrismaResolvedCredentials,
10} from "./AuthProvider.ts";
11
12export interface PrismaEnvironmentShape extends PrismaResolvedCredentials {
13 baseUrl: string;
14}
15
16export class PrismaEnvironment extends Context.Service<
17 PrismaEnvironment,
18 PrismaEnvironmentShape
19>()("Prisma::PrismaEnvironment") {}
20
21const DEFAULT_BASE_URL = "https://api.prisma.io";
22const PRISMA_API_URL_ENV = "PRISMA_API_URL";
23const PRISMA_MANAGEMENT_API_URL_ENV = "PRISMA_MANAGEMENT_API_URL";
24
25const isLoopbackHost = (hostname: string) =>
26 hostname === "localhost" ||
27 hostname.endsWith(".localhost") ||
28 hostname === "127.0.0.1" ||
29 hostname === "[::1]";
30
31const normalizeBaseUrl = (value: string) =>
32 Effect.try({
33 try: () => {
34 const url = new URL(value);
35 if (url.protocol !== "https:" && url.protocol !== "http:") {
36 throw new Error("Prisma Management API URL must use HTTP or HTTPS.");
37 }
38 if (url.username.length > 0 || url.password.length > 0) {
39 throw new Error(
40 "Prisma Management API URL must not contain credentials.",
41 );
42 }
43 if (url.protocol === "http:" && !isLoopbackHost(url.hostname)) {
44 throw new Error(
45 "Prisma Management API URL must use HTTPS unless it targets a loopback host.",
46 );
47 }
48 if (
49 (url.pathname !== "/" && url.pathname !== "") ||
50 url.search.length > 0 ||
51 url.hash.length > 0
52 ) {
53 throw new Error(
54 "Prisma Management API URL must be an origin without a path, query, or fragment.",
55 );
56 }
57 return url.origin;
58 },
59 catch: (cause) =>
60 cause instanceof Error
61 ? cause
62 : new Error(`Invalid Prisma Management API URL: ${String(cause)}`),
63 });
64
65export const fromProfile = () =>
66 Layer.effect(
67 PrismaEnvironment,
68 Effect.gen(function* () {
69 const baseUrl = yield* Config.String(PRISMA_API_URL_ENV).pipe(
70 Config.orElse(() => Config.String(PRISMA_MANAGEMENT_API_URL_ENV)),
71 Config.withDefault(DEFAULT_BASE_URL),
72 Effect.flatMap(normalizeBaseUrl),
73 );
74 const { resolve } = yield* resolveProviderConfig<
75 PrismaAuthConfig,
76 PrismaResolvedCredentials
77 >(PRISMA_AUTH_PROVIDER_NAME);
78 const credentials = yield* resolve;
79 return { ...credentials, baseUrl };
80 }),
81 );