1import * as machines from "@distilled.cloud/fly-io/machines";
2import * as Config from "effect/Config";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Redacted from "effect/Redacted";
6import * as FetchHttpClient from "effect/http/FetchHttpClient";
7import { CredentialsFromAmbientOrEnv } from "./Credentials.ts";
8import { GetSecret } from "./GetSecret.ts";
9import { makeHttpSecretBinding } from "./SecretHttp.ts";
10
11/**
12 * HTTP implementation of {@link GetSecret}. Provide it on the
13 * {@link Service} or Action Effect.
14 *
15 *
16 * ### Provide the layer
17 * **Example:** On a Service
18 * ```typescript
19 * Effect.gen(function* () {
20 * const get = yield* Fly.GetSecret(ApiToken);
21 * // ...
22 * }).pipe(Effect.provide(Fly.GetSecretHttp))
23 * ```
24 *
25 * @layer
26 * @product Secret
27 * @provides Fly.GetSecret
28 */
29export const GetSecretHttp = Layer.effect(
30 GetSecret,
31 Effect.suspend(() =>
32 makeHttpSecretBinding({
33 makeClient: (auth, appName, secretName) =>
34 Effect.fn("Fly.GetSecret")(function* () {
35 const name = yield* secretName;
36 if (globalThis.__ALCHEMY_RUNTIME__) {
37 // Fly injects App secrets as env vars on the Machine. Prefer
38 // that over getSecret — org tokens still cannot read plaintext
39 // from outside the App, and the env is already the source of
40 // truth once the secret exists.
41 const fromEnv = yield* Config.Redacted(name).pipe(
42 Effect.map((value) => ({
43 name,
44 value: Redacted.value(value),
45 })),
46 Effect.catch(() => Effect.succeed(undefined)),
47 );
48 if (fromEnv !== undefined) return fromEnv;
49 }
50 return yield* auth.authorize(
51 machines.getSecret({
52 app_name: yield* appName,
53 secret_name: name,
54 show_secrets: globalThis.__ALCHEMY_RUNTIME__ === true,
55 }),
56 );
57 }),
58 }),
59 ),
60).pipe(
61 Layer.provide(FetchHttpClient.layer),
62 Layer.provide(CredentialsFromAmbientOrEnv),
63);