Secret resolvers
Resolve secrets lazily at call time with env() and credential.file() instead of hard-coding them.
Every auth strategy takes a secret as a reference, not a value, and resolves it
at call time — reach for env() and credential.file() so the declaration carries a
capability, not a credential you have to commit.
The resolvers are two namespaces, not four loose functions. env is the
environment-variable source and carries its own requiredness modifier
(env('NAME') throws when unset, env.optional('NAME') resolves to absent);
credential groups the sources that are not the process environment
(credential.file, credential.from).
Example
import { } from 'stitchapi';
import { , , } from 'stitchapi/auth';
// env() reads process.env.API_TOKEN when the stitch is called, not now.
const = ({
: 'https://api.example.com',
: '/me',
: (('API_TOKEN')),
});
// credential.file() resolves from ~/.stitch/secrets.json, falling back to the env var.
const = ({
: 'https://api.example.com',
: '/billing',
: (.('BILLING_TOKEN')),
});The token is never read at declaration time, so the secret never enters the stitch's source — only the name of where to find it does.
Options
A secret is a string | (() => string). Every strategy — bearer, apiKey,
basic, oauth2 — accepts one and resolves it the moment a request goes out,
so the same declaration works across processes and machines without baking a
value in.
env(name) returns a thunk that reads process.env[name] at call time and
throws missing env var <name> if it is unset — failing loudly at the call
rather than silently sending an empty token.
env.optional(name) is the same source without the throw: it resolves to
undefined when the variable is unset or empty, and bearer then sends the
request unauthenticated rather than failing. See
Optional credentials.
credential.file(name) reads ~/.stitch/secrets.json if present and the key is
there, otherwise falls back to the environment variable of the same name, and
throws if neither has it.
credential.from(source, name) resolves from an injected source instead — an
object with a get(name) method (a NestJS ConfigService, a secrets-manager
client) or a plain (name) => value function — for apps that supply config
without ever touching process.env. Like env(), it throws on a missing or
empty value.
~/.stitch/secrets.json is unencrypted plaintext JSON. Restrict its
permissions (chmod 600 ~/.stitch/secrets.json), add it to your
.gitignore, and never commit it. For production workloads prefer a proper
secrets manager or env() backed by your deployment platform's secret
injection.
Why call-time resolution matters: the value is fetched only when a call is
made, so you never commit a secret and the stitch holds a capability rather than
the credential itself — the capability boundary
the product is built on. You can pass a plain string (discouraged — that hard-codes
the value), or any () => string thunk of your own, so plugging in a vault or
secrets manager is just supplying a different resolver.
Anti-pattern: don't pass a secret as a plain string — it is read at
declaration time and lives in your source and history, collapsing the
capability back into a committed credential; pass env() or
credential.file() instead so the value resolves at call time and the
stitch carries a capability, not a credential. See Capability, not
credential.