@stackstart/sdk
v0.1.0
Published
Auto-load the stack start credential bundle into process.env at runtime.
Maintainers
Readme
@stackstart/sdk
Auto-load your stack start credential bundle into process.env at runtime. Zero runtime dependencies — uses Node's built-in fetch and fs/promises.
Install
pnpm add @stackstart/sdk
# or
npm install @stackstart/sdkSet your master key:
export STACKSTART_KEY=ss_...Three modes
1. Side-effect import (recommended)
import "@stackstart/sdk/load";That single line, at the top of your app entry, fetches the bundle from https://stackstart.co/api/keys/resolve and merges every resolved env var into process.env. Existing process.env values are never overwritten — your shell, CI dashboard, or .env.local always wins.
If stack start is unreachable, the load is a no-op (a sanitized warning is printed to stderr). Your app boots either way; the host's own runtime checks ("DATABASE_URL is required") will fire normally.
Want to wait until the bundle is fetched before reading env vars?
import { ready } from "@stackstart/sdk/load";
await ready;
console.log(process.env.DATABASE_URL);2. Programmatic API
import { resolve, env, pull, StackStartError } from "@stackstart/sdk";
// Pure: returns the bundle, doesn't touch process.env.
const bundle = await resolve();
// Side-effecting: merges into process.env (doesn't overwrite existing values).
await env();
// Persists to disk as .env.local (or any path).
await pull({ outFile: ".env.production" });
try {
await env();
} catch (err) {
if (err instanceof StackStartError) {
// err.code is one of:
// MISSING_KEY | INVALID_KEY_FORMAT | UNAUTHORIZED |
// RATE_LIMITED | SERVER_ERROR | NETWORK_ERROR
}
}resolve() and env() cache the bundle in-memory for 300s by default. Pass ttlSeconds: 0 to disable, or force: true to bypass for a single call.
3. CLI
npx stackstart pull # writes .env.local
npx stackstart pull --out .env.dev # custom path
npx stackstart pull --force # overwrite a hand-edited file
npx stackstart pull --json # dump to stdout for pipingUse this for tools that bypass the runtime — drizzle-kit, prisma, build-time scripts. They read .env.local directly and never see your in-process env mutations.
The CLI:
- Reads
STACKSTART_KEYfrom your shell env, falling back to.env.localin cwd. - Adds a banner:
# Generated by 'npx stackstart pull' on <ISO>. DO NOT COMMIT. Refresh with 'npx stackstart pull --force'. - Refuses to overwrite a file containing user-authored lines unless
--forceis given. - Warns if the output path doesn't appear to be gitignored.
Exit codes: 0 success, 1 auth/network failure, 2 bad arguments.
Security model
- In-memory by default. The side-effect import and
env()never touch disk. The bundle lives only inprocess.envand the SDK's TTL cache. - Disk only when explicit. Only
pull(programmatic or CLI) writes a file. The output ischmod 600. - Never logged. The SDK never emits the master key or any resolved value. Error messages run through a sanitizer that redacts anything matching
ss_<chars>orBearer <chars>. - No persistence beyond the process. Restart the app — the SDK fetches fresh.
Wiring into Next.js
Use instrumentation.ts:
// instrumentation.ts (in the project root, next to app/)
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("@stackstart/sdk/load");
}
}Why instrumentation.ts?
- It runs once per server process, before any route handler, page render, or
getServerSideProps. Anything that readsprocess.envafterwards sees the merged bundle. - The
NEXT_RUNTIME === "nodejs"guard skips the Edge runtime, where the SDK'sfs/promisesusage (for the optional CLI codepath) isn't available. (The/loadentry itself only usesfetch, but being explicit is cheap.) - Awaiting the dynamic import means the bundle is in-process before the first request — no race.
If you'd rather load eagerly without awaiting, just do import "@stackstart/sdk/load" at the top of instrumentation.ts. The side-effect module exports a ready Promise you can await later.
Wiring into production hosts
Vercel, Render, Fly, Cloudflare Workers (Node): keep @stackstart/sdk/load in instrumentation.ts. Set STACKSTART_KEY as a project env var in the dashboard. The bundle is fetched at boot and lives in memory.
Static or build-time tools (drizzle-kit, prisma migrate, Next.js output: "export"): these run before the runtime loads. Use the CLI in your build step:
# package.json
{
"scripts": {
"build": "stackstart pull --out .env.production && next build"
}
}Or pipe --json into whatever consumer you like:
stackstart pull --json | jq -r 'to_entries[] | "\(.key)=\(.value)"' > .env.productionVersioning
0.x — API may change without a major bump. Pin exactly ("@stackstart/sdk": "0.1.0") until 1.0. After 1.0 the three entry surfaces (@stackstart/sdk, @stackstart/sdk/load, stackstart CLI) are SemVer-stable.
License
MIT
