1import * as Effect from "effect/Effect";
2import * as Output from "../Output.ts";
3import type { ProviderMode } from "../ProviderMode.ts";
4import { isResource, type ResourceLike } from "../Resource.ts";
5import type { AIGatewayProps } from "./AIGateway.ts";
6
7type CredentialScope = Pick<AIGatewayProps, "branch" | "project">;
8
9export const scopeIdentity = (scope: CredentialScope) => {
10 const source = scope.branch ?? scope.project;
11 if (isResource(source)) return `${source.Type}:${source.FQN}`;
12 if (Effect.isEffect(source) || Output.isOutput(source)) return undefined;
13 if (
14 typeof scope.branch?.projectId === "string" &&
15 typeof scope.branch.branchId === "string"
16 ) {
17 return `branch:${scope.branch.projectId}:${scope.branch.branchId}`;
18 }
19 if (typeof scope.project?.projectId === "string")
20 return `project:${scope.project.projectId}`;
21 return undefined;
22};
23
24/** Unknown scope identity falls back to a managed credential. */
25export const usesInjectedCredentials = (
26 host: Pick<ResourceLike, "Type" | "Props" | "Mode"> | undefined,
27 target: CredentialScope,
28 defaultMode: ProviderMode,
29) => {
30 if (host?.Type !== "Neon.Function" || (host.Mode ?? defaultMode) !== "live")
31 return false;
32 const current: CredentialScope | undefined = host.Props;
33 if (!current || Effect.isEffect(current) || Output.isOutput(current))
34 return false;
35 return (
36 (current.branch !== undefined && current.branch === target.branch) ||
37 (current.project !== undefined && current.project === target.project) ||
38 (scopeIdentity(current) !== undefined &&
39 scopeIdentity(current) === scopeIdentity(target))
40 );
41};