import * as machines from "@distilled.cloud/fly-io/machines"; import * as Effect from "effect/Effect"; import * as Redacted from "effect/Redacted"; import * as Provider from "../Provider.ts"; import { Resource } from "../Resource.ts"; import { App } from "./App.ts"; import type { Providers } from "./Providers.ts"; /** * A resource-valued prop: the resource itself, or an Effect that produces * it (so `yield* App(...)` and `App(...)` both type-check). */ type Ref = T | Effect.Effect; export interface SecretProps { /** * Parent Fly App. Changing it replaces the secret. */ app: Ref; /** * Secret name. Used as the Machine env-var name when set by the user * (case-sensitive, stored as-is). If omitted, a unique name is generated * from the stack, stage and logical ID (the ownership stamp). Changing * it replaces the secret. */ name?: string; /** * Secret value. Wrap with `Redacted.make(...)` so it is never logged. * Updated in place via `updateSecrets`. Never persisted in attributes. */ value: Redacted.Redacted | string; } export type Secret = Resource<"Fly.Secret", SecretProps, { /** Parent Fly App name. */ appName: string; /** Secret name (unique per App). */ name: string; /** Fly digest of the current value. Not the plaintext. */ digest: string | undefined; /** RFC3339 creation timestamp. */ createdAt: string | undefined; /** RFC3339 last-update timestamp. */ updatedAt: string | undefined; }, never, Providers>; declare const SecretResource: import("../Resource.ts").ResourceClass; /** * A Fly.Secret is an App vault entry. Fly injects it as an environment * variable on every Machine. Use it when the value is shared and * managed in one place by Fly. * * For a secret only this {@link Service} reads from `.env` at deploy * time, yield `Config.redacted` instead. Do not pass `env: { ... }` on * a Service. * * @see https://fly.io/docs/apps/secrets/ * * ### Config.redacted on a Service * Most secrets in a Service come from your `.env`. Yield * `Config.redacted` in init. Alchemy binds the value onto the Machine. * * **Example:** Bind from .env * ```typescript * import * as Config from "effect/Config"; * import * as Redacted from "effect/Redacted"; * * export default class Api extends Fly.Service()( * "Api", * { app: Site, main: import.meta.url, port: 3000 }, * Effect.gen(function* () { * const apiKey = yield* Config.redacted("API_KEY"); * * return { * fetch: Effect.gen(function* () { * const token = Redacted.value(apiKey); * return HttpServerResponse.text("ok"); * }), * }; * }), * ) {} * ``` * * ### Create a Secret * Wrap the value with `Redacted.make` so it is never logged. The * plaintext is never stored in attributes. Omit `name` and Alchemy * generates an ownership-stamped name. * * **Example:** Generated name * ```typescript * const dbUrl = yield* Fly.Secret("DatabaseUrl", { * app: Site, * value: Redacted.make("postgres://…"), * }); * ``` * * :::caution[Changing `app` replaces the Secret] * The value is created on the new App. The old name is deleted. * ::: * * ### Env-var name * `name` is the env-var Machines see. It is stored as-is * (case-sensitive). * * **Example:** Explicit name * ```typescript * export const ApiToken = Fly.Secret("ApiToken", { * app: Site, * name: "API_TOKEN", * value: Redacted.make("sk_live_…"), * }); * ``` * * :::caution[Changing `name` replaces the Secret] * Fly cannot rename a secret. Alchemy creates the new name, then * deletes the old one. * ::: * * ### Rotate the value * Updating `value` is in place via `updateSecrets`. * * **Example:** New value * ```typescript * export const ApiToken = Fly.Secret("ApiToken", { * app: Site, * name: "API_TOKEN", * value: Redacted.make("sk_live_rotated"), * }); * ``` * * ### Get a secret at runtime * {@link GetSecret} is bound to one Secret. Provide * {@link GetSecretHttp}. Fly only returns plaintext from a Machine in * the same App. From a deploy-time Action you get metadata (name, * digest, timestamps). * * **Example:** GetSecret * ```typescript * const get = yield* Fly.GetSecret(ApiToken); * const got = yield* get(); * ``` * * ### List secrets * {@link ListSecrets} is bound to an {@link App}. From an Action, the * org token can list any App in the org. From a Machine, deploy tokens * are per-App. Mixing Apps on one Machine shares one `FLY_API_TOKEN` * and is not supported. * * **Example:** ListSecrets * ```typescript * const list = yield* Fly.ListSecrets(Site); * const { secrets } = yield* list(); * ``` * * ### Write secrets * {@link WriteSecret} creates, updates, and deletes by name. Provide * {@link WriteSecretHttp} on the Action or Service Effect. * * **Example:** Rotate from an Action * ```typescript * const Seed = Alchemy.Action( * "Seed", * Effect.gen(function* () { * const secrets = yield* Fly.WriteSecret(ApiToken); * * return Effect.fn(function* () { * yield* secrets.update("API_TOKEN", Redacted.make("sk_live_rotated")); * }); * }).pipe(Effect.provide(Fly.WriteSecretHttp)), * ); * ``` * * @resource */ export declare const Secret: typeof SecretResource; declare const SecretNotCreated_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "Fly.SecretNotCreated"; } & Readonly; export declare class SecretNotCreated extends SecretNotCreated_base<{ appName: string; name: string; }> { } declare const SecretAppRequired_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "Fly.SecretAppRequired"; } & Readonly; export declare class SecretAppRequired extends SecretAppRequired_base<{ message: string; }> { } export declare const SecretProvider: () => import("effect/Layer").Layer, never, import("../Stack.ts").Stack | import("../Stage.ts").Stage | machines.FlyIoOpContext>; export {}; //# sourceMappingURL=Secret.d.ts.map