import { Schema } from 'effect'; /** * The type-erased field shape, used where the value type is irrelevant (the * declared map, iteration). It is NOT `EnvFieldSpec` because * effect's `Schema` is invariant in its decoded type — a concrete * `EnvFieldSpec` would not widen to that. Typing the schema as * `Schema.Schema.Any` lets any concrete field assign here. */ export declare interface AnyEnvFieldSpec { readonly kind: EnvFieldKind; readonly access: EnvAccess; readonly schema: Schema.Schema.Any; readonly tsType: string; readonly optional: boolean; readonly default?: string | undefined; readonly description?: string | undefined; readonly example?: string | undefined; readonly generate?: 'base64url' | 'hex' | undefined; } /** Project an app's `defineEnv` contract onto manifest entries. */ export declare const appEnvEntries: (contract: EnvContract | undefined) => ReadonlyArray; /** * Enforce the public-prefix invariant (t3-env's client-prefix rule, * framework-branded). Returns naming errors to merge with the value errors: * * - On a WEB app, every `access: 'public'` var (browser-exposed) MUST start * with the contract's `publicPrefix` (default `VOLTRO_PUBLIC_`). * - ANY `access: 'secret'` var MUST NOT start with the prefix (the prefix * marks browser-bound public values; a secret carrying it is a contradiction). * * `publicPrefix: false` opts out. API apps skip the public-prefix requirement * (their public vars are non-sensitive SERVER config, never browser-bound) but * still get the secret-must-not-be-prefixed check. */ export declare const checkEnvNaming: (contract: EnvContract | undefined, appType: "api" | "web") => ReadonlyArray; /** A plugin's declared env need — metadata only, NOT a read path. Mirror what * the plugin actually reads via `options.X ?? process.env.X`. */ export declare interface DeclaredEnvVar { readonly name: string; readonly required: boolean; readonly secret: boolean; readonly description?: string; readonly example?: string; } /** The framework's default prefix for browser-exposed PUBLIC env vars. */ export declare const DEFAULT_PUBLIC_PREFIX = "VOLTRO_PUBLIC_"; /** * Declare an app's environment contract. Call this in `app.config.ts` and put * the result on the default export's `env` field: * * ```ts * // app.config.ts * import { defineEnv, envVar } from '@voltro/env' * * export const env = defineEnv({ * SENTRY_DSN: envVar.string({ access: 'public' }), * STRIPE_KEY: envVar.string({ access: 'secret' }), * }) * * export default { type: 'api', name: 'myApi', env } * ``` * * The returned contract is the single source of truth: the CLI reads it to * validate `process.env` at boot, to bake the public subset into the web * bundle, to generate the typed `@voltro/env/server` + `@voltro/env/public` * accessors, and to emit `.env.example` + the inspect manifest. * * You do NOT read values off the contract directly — read `serverEnv` / * `getSecret` from `@voltro/env/server` (server) or `publicEnv` from * `@voltro/env/public` (browser). * * `M` is inferred from the literal map, so the contract's phantom value types * are exact (`{ SENTRY_DSN: string; STRIPE_KEY: string }` above). */ export declare const defineEnv: (vars: M, options?: DefineEnvOptions) => EnvContract; export declare interface DefineEnvOptions { /** * Required name prefix for browser-exposed public vars. Default * `VOLTRO_PUBLIC_`. Pass a different string to brand it your own way, or * `false` to disable the prefix invariant entirely. */ readonly publicPrefix?: string | false; } declare const ENV_CONTRACT_BRAND: unique symbol; /** Whether a variable may cross the browser boundary. */ export declare type EnvAccess = 'public' | 'secret'; /** * The frozen result of `defineEnv(...)`. Carries the declaration the CLI * reads at boot, plus phantom type members the framework's codegen uses to * derive the typed `@voltro/env/server` + `@voltro/env/public` accessors. * * You reference it from `app.config.ts` (`export const env = defineEnv(...)`, * then `env` on the default export). You do NOT read values off it directly — * read `serverEnv` / `getSecret` (server) or `publicEnv` (browser). */ export declare interface EnvContract { readonly [ENV_CONTRACT_BRAND]: true; /** The declared variables, keyed by env-var name. */ readonly vars: M; /** * Required name prefix for BROWSER-EXPOSED public vars (default * `VOLTRO_PUBLIC_`). On a web app, every `access: 'public'` var (which ships * to the browser) MUST carry this prefix, and no `access: 'secret'` var may — * so the name and the access tag can never silently disagree (t3-env's * client-prefix invariant, framework-branded). `false` opts out of the check. */ readonly publicPrefix: string | false; /** Phantom — resolved server value shape (never present at runtime). */ readonly _values?: EnvValues; /** Phantom — browser-visible (public) value shape. */ readonly _public?: PublicEnvValues; } /** The concrete coercion a field performs on the raw string env value. */ export declare type EnvFieldKind = 'string' | 'number' | 'boolean' | 'enum' | 'url' | 'port' | 'secret'; /** * A single declared environment variable. Produced by the `envVar.*` * builders — you never construct this by hand. `A` is the decoded value * type; `Optional` lifts "may be absent" to the type level so the inferred * accessor is `A | undefined` for optional vars and `A` otherwise. */ export declare interface EnvFieldSpec { readonly kind: EnvFieldKind; /** Browser boundary classification — mandatory, no default. */ readonly access: EnvAccess; /** effect/Schema that decodes the RAW string env value → typed `A`. */ readonly schema: Schema.Schema; /** * TypeScript type literal for the codegen'd typed accessors — e.g. * `"string"`, `"number"`, `"'dev' | 'prod'"`. Bounded to what the * builders emit, so the generated `.d.ts` augmentation is precise. */ readonly tsType: string; /** When true, an unset var (no value, no default) is allowed → `A | undefined`. */ readonly optional: Optional; /** Fallback applied (in raw string form) when the env var is unset. */ readonly default?: string | undefined; /** One-line description — surfaced in `.env.example` + the inspect manifest. */ readonly description?: string | undefined; /** Example value for `.env.example`. NEVER a real secret — a placeholder. */ readonly example?: string | undefined; /** * Encoding to MINT this secret in, when the framework is allowed to generate * it. Set only by `envVar.secret({ generate })`. * * The distinction it draws is the whole point: a session-signing key is ours * to invent, so `voltro dev` can mint a per-project one on first boot and the * template never has to ship a placeholder. A third-party credential (a * WorkOS API key) is equally secret and equally required, but inventing one * would produce a value that merely LOOKS right — so it stays absent, the * boot gate fails, and a human goes and fetches the real one. */ readonly generate?: 'base64url' | 'hex' | undefined; } export declare interface EnvManifestEntry { readonly key: string; readonly owner: EnvOwner; /** `'secret'` → never print the value (sensitive: token / password / URL * with credentials). `'public'` → a non-sensitive knob. For app vars this * is also the browser-bundle boundary. */ readonly access: EnvAccess; /** Required (no default, not optional). Drives `.env.example` ordering + * the "missing required" boot report. */ readonly required: boolean; readonly description?: string; readonly example?: string; readonly default?: string; /** App fields carry their coercion kind; framework/plugin entries omit it. */ readonly kind?: EnvFieldKind; /** Framework grouping for `.env.example` section headers. */ readonly group?: string; } export declare type EnvOwner = 'app' | 'framework' | `plugin:${string}`; export declare interface EnvResolveError { readonly key: string; readonly access: EnvAccess; /** `'missing'` (required but unset) or `'invalid'` (failed the Schema). */ readonly reason: 'missing' | 'invalid'; readonly message: string; } /** The declared variable map passed to `defineEnv`. */ export declare type EnvSchemaMap = Record; /** The fully-resolved server value shape for a contract (public + secret). */ export declare type EnvValues = { readonly [K in keyof M]: InferEnvValue; }; /** * The field-builder surface used inside `defineEnv({...})`. * * ```ts * env: defineEnv({ * SENTRY_DSN: envVar.string({ access: 'public', description: 'Browser Sentry DSN' }), * APP_TITLE: envVar.string({ access: 'public', default: 'Voltro' }), * PORT: envVar.port({ access: 'public', default: '4000' }), * FEATURE_X: envVar.boolean({ access: 'public', default: 'false' }), * LOG_LEVEL: envVar.enum(['debug', 'info', 'warn'], { access: 'public', default: 'info' }), * STRIPE_KEY: envVar.string({ access: 'secret' }), * WEBHOOK_URL: envVar.url({ access: 'secret', optional: true }), * }) * ``` */ export declare const envVar: { /** A plain string. */ readonly string: (opts: FieldOptions) => EnvFieldSpec; /** A number, coerced from its string form (`"5"` → `5`). */ readonly number: (opts: FieldOptions) => EnvFieldSpec; /** A TCP port — number in `1..65535`, coerced from string. */ readonly port: (opts: FieldOptions) => EnvFieldSpec; /** A boolean, coerced from `true/false/1/0/yes/no/on/off`. */ readonly boolean: (opts: FieldOptions) => EnvFieldSpec; /** * A real secret — server-only, required, and long enough to BE a secret. * * Exists because the failure this prevents is invisible: a deployment that * forgets a signing key does not crash, it quietly signs with a placeholder, * and everything looks fine until someone forges a session. Declaring the * variable makes the process refuse to boot instead, which is the only * moment anybody is actually watching. * * `access` is forced to `'secret'` — a value with a length floor is never * something you meant to bundle into a browser. There is deliberately no * `default`: a default secret is not a secret. * * Pass `generate` when the secret is OURS to invent (a signing key, an * encryption key). `voltro dev` then mints a per-project value into * `.env.local` on first boot, which is why no template needs to ship a * placeholder. Leave it off for anything issued by a third party. */ readonly secret: (opts: Omit, "access" | "default" | "optional"> & SecretFieldOptions) => EnvFieldSpec; /** A URL string, validated with the `URL` constructor. */ readonly url: (opts: FieldOptions) => EnvFieldSpec; /** A string constrained to a closed set — typed as the literal union. */ readonly enum: (values: T, opts: FieldOptions) => EnvFieldSpec; }; /** Common options every field accepts. `O` lifts `optional` to the type level. */ export declare interface FieldOptions { /** Browser boundary classification — MANDATORY. `'public'` is bundled into * the browser; `'secret'` stays server-only. There is no default. */ readonly access: EnvAccess; /** Allow the variable to be absent (no value, no default). Makes the * accessor type `A | undefined`. Use `default` instead when there is a * sensible fallback. */ readonly optional?: O; /** Fallback (raw string) applied when the env var is unset. With a default * the value is always present, so the accessor type stays `A`. */ readonly default?: string; /** One-line description — shown in `.env.example` + the inspect manifest. */ readonly description?: string; /** Example value for `.env.example`. Never a real secret — a placeholder. */ readonly example?: string; } /** * Render aggregated errors into a single multi-line message for a boot abort. * Mirrors the per-plugin config-decode failure shape so the boot log reads * consistently across config validation surfaces. */ export declare const formatEnvErrors: (errors: ReadonlyArray) => string; /** * The framework's own user-facing env vars. Curated, not exhaustive — the * goal is "what an operator configures", not every internal/test knob. This * is the single source for the `.env.example` framework section + the turbo * `globalPassThroughEnv` list. Add new operator-facing framework vars HERE. */ export declare const FRAMEWORK_ENV_CATALOG: ReadonlyArray; /** Just the framework var NAMES — the source for turbo `globalPassThroughEnv`. */ export declare const frameworkEnvNames: () => ReadonlyArray; /** True once the boot gate has installed a snapshot. */ export declare const hasEnvSnapshot: () => boolean; /** Whether a live rotation is in its overlap window for `key` (previous still * valid). Prunes an elapsed window as a side effect, same as the read. */ export declare const hasLiveOverlap: (key: string) => boolean; /** Lift a field's optionality + value type to the accessor's value type. */ export declare type InferEnvValue = F extends EnvFieldSpec ? (O extends true ? A | undefined : A) : never; /** Install the boot-resolved value map. Called once by the CLI after the * env-validation gate passes. Idempotent: a second install replaces the * first (a supervised dev restart re-resolves cleanly) AND clears any live * overlay — a fresh boot re-resolves every value, so a stale rotation must * not survive it. */ export declare const installEnvSnapshot: (values: Record) => void; /** Runtime type guard: is `value` an `EnvContract`? Used by the CLI loader, * which sees the app.config default export as `unknown`. */ export declare const isEnvContract: (value: unknown) => value is EnvContract; export declare interface PluginEnv { /** The declared variables — assign this to the plugin's `declaredEnv`. */ readonly declared: ReadonlyArray; /** * Read a DECLARED env var: `override ?? process.env[name]`. Throws if `name` * is not in the declaration — so a typo or an undeclared read fails loudly at * the call site instead of silently drifting from the manifest. */ read(name: string, override?: string): string | undefined; } export declare const pluginEnv: (declared: ReadonlyArray) => PluginEnv; /** Project a plugin's `declaredEnv` onto manifest entries. */ export declare const pluginEnvEntries: (pluginName: string, declared: ReadonlyArray | undefined) => ReadonlyArray; /** The browser-visible value shape (public fields only). */ export declare type PublicEnvValues = { readonly [K in PublicKeys]: InferEnvValue; }; /** Keys of `M` whose field is `access: 'public'`. */ export declare type PublicKeys = { [K in keyof M]: M[K]['access'] extends 'public' ? K : never; }[keyof M]; /** Read a resolved value. A live-rotated value wins over the frozen snapshot. * Returns `undefined` for unknown / unset keys. */ export declare const readEnvValue: (key: string) => unknown; /** * The PREVIOUS value of a live-rotated key, while its grace window is open. * * A verifier that must accept work created just before a rotation (a signature, * a token, a ciphertext) reads BOTH the current value and this one during * cutover — the same current/previous overlap sessions/webhooks use. Returns * `undefined` once the window has elapsed (the value is REVOKED, and pruned on * read so it cannot linger), or when the key was never rotated with an overlap. */ export declare const readEnvValuePrevious: (key: string) => unknown; /** * Install a live-rotated value over the frozen snapshot. * * `previous` + `graceMs` record the pre-rotation value as valid for a grace * window: `readEnvValuePrevious` returns it until the window elapses, then * revokes it. Omit them for a hard cutover with no overlap. Requires a boot * snapshot to already exist — a live refresh is a POST-boot operation, and * refreshing before boot is the same wiring bug `requireEnvValue` names. */ export declare const refreshEnvValue: (key: string, value: unknown, options?: { readonly previous?: unknown; readonly graceMs?: number; }) => void; /** * Render a grouped `.env.example`. App + plugin entries come first (the things * a developer must fill in), then the framework catalog grouped by area. */ export declare const renderDotEnvExample: (entries: ReadonlyArray, opts?: RenderDotEnvOptions) => string; export declare interface RenderDotEnvOptions { /** Header comment lines (without the leading `# `). */ readonly header?: ReadonlyArray; /** Include the framework catalog section. Default true. */ readonly includeFramework?: boolean; } /** * Read a resolved value, throwing a clear error if the snapshot is missing. * The missing-snapshot case means env was read before the boot gate ran (e.g. * at module-import time in a context the framework doesn't drive) — almost * always a wiring bug, so we fail loudly rather than hand back `undefined`. */ export declare const requireEnvValue: (key: string) => unknown; /** Reset the snapshot + any live overlay + the clock (tests). */ export declare const resetEnvSnapshot: () => void; export declare interface ResolvedEnv { /** Every resolved value (public + secret), keyed by env-var name. Optional * vars that are absent appear as `undefined`. */ readonly values: Record; /** The public subset only — the exact, secret-free map that may be baked * into the browser bundle. */ readonly publicValues: Record; /** Decode failures + missing-required, aggregated so the CLI can report * ALL problems at once instead of crashing on the first. */ readonly errors: ReadonlyArray; /** Convenience: `errors.length === 0`. */ readonly ok: boolean; } export declare const resolveEnv: (opts: ResolveEnvOptions) => Promise; export declare interface ResolveEnvOptions { /** The contract from `app.config.ts`. `undefined` (no `env` field) → an * empty, OK result, so the gate is a no-op for apps that declare nothing. */ readonly contract: EnvContract | undefined; /** Read a raw public value. Defaults to `process.env`. */ readonly readEnv?: (key: string) => string | undefined; /** Resolve a raw secret value through the configured backend. Defaults to * `readEnv` (so without a backend, secrets just come from `process.env`). */ readonly resolveSecret?: (key: string) => Promise; } /** Extra options only `envVar.secret` accepts. */ declare interface SecretFieldOptions { /** Minimum accepted length. Default 32 — the floor for HMAC-SHA256, and high * enough to catch hand-typed placeholders like `change-me`. */ readonly minLength?: number; /** * Let the framework MINT this secret in the named encoding when it is unset * in development (`voltro dev` writes it once to `.env.local`). Omit for a * secret that comes from somewhere else — a third-party API key must fail * the boot gate, not be invented. */ readonly generate?: 'base64url' | 'hex'; } /** Test seam — override the clock the grace window measures against. */ export declare const setEnvClock: (fn: () => number) => void; export { }