Custom / Remote Secrets
Generic resolver for custom secret providers, testing, and unsupported integrations.
Overview
fromRemoteSecrets is the generic escape hatch for custom integrations, testing, and secret providers not covered by the built-in resolvers. You provide your own SecretClient implementation.
import { createEnv, requiredString, fromRemoteSecrets } from "@ayronforge/better-env"
import type { SecretClient } from "@ayronforge/better-env"
import { Effect } from "effect"
const client: SecretClient = {
getSecret: async (id) => {
// Fetch from your custom provider
const response = await fetch(`https://secrets.example.com/v1/${id}`)
if (!response.ok) return undefined
return response.text()
},
}
const envEffect = createEnv({
server: {
DATABASE_URL: requiredString,
API_KEY: requiredString,
},
resolvers: [
fromRemoteSecrets({
secrets: {
DATABASE_URL: "prod/database-url",
API_KEY: "prod/api-key",
},
client,
}),
],
})
const env = await Effect.runPromise(envEffect)
No peer dependencies required — fromRemoteSecrets is exported directly from @ayronforge/better-env.
Options
| Name | Type | Default | Description |
|---|---|---|---|
| secrets Required | Record<string, string> | — | Map of env var names to secret identifiers passed to the client. |
| client Required | SecretClient | — | Your custom client implementing the SecretClient interface. |
SecretClient interface
interface SecretClient {
getSecret: (secretId: string) => Promise<string | undefined>
getSecrets?: (secretIds: string[]) => Promise<Map<string, string | undefined>>
}
getSecret— required. Fetches a single secret by ID.getSecrets— optional. When provided and there are multiple secrets to resolve, the resolver uses this for batch fetching instead of making concurrentgetSecretcalls.
Batch optimization
If your SecretClient implements getSecrets, the resolver automatically uses it when resolving more than one secret. For a single secret, getSecret is always used.
const batchClient: SecretClient = {
getSecret: async (id) => {
const res = await fetch(`https://secrets.example.com/v1/${id}`)
return res.ok ? res.text() : undefined
},
getSecrets: async (ids) => {
const res = await fetch("https://secrets.example.com/v1/batch", {
method: "POST",
body: JSON.stringify({ ids }),
})
const data = await res.json()
return new Map(Object.entries(data))
},
}