@cfast/env
Overview
Section titled “Overview”Every Cloudflare Worker has bindings — D1 databases, KV namespaces, R2 buckets, secrets, and environment variables. By default they are loosely typed, and misconfiguration surfaces as a runtime crash buried in a stack trace.
@cfast/env provides a single schema definition that produces TypeScript types and runtime validation in one place. Missing or misconfigured bindings are caught at Worker startup, before any request is processed, so you never find out about a missing secret at 3am.
Installation
Section titled “Installation”pnpm add @cfast/envNo peer dependencies required.
Quick Setup
Section titled “Quick Setup”Declare your bindings, initialize once in the Worker entry point, and use the typed result everywhere:
import { defineEnv } from "@cfast/env";
const env = defineEnv({ DB: { type: "d1" }, CACHE: { type: "kv" }, UPLOADS: { type: "r2" }, MAILGUN_API_KEY: { type: "secret" }, APP_URL: { type: "var", default: "http://localhost:8787" },});
export default { async fetch(request, rawEnv) { env.init(rawEnv); const { DB, MAILGUN_API_KEY, APP_URL } = env.get(); // ^-- D1Database ^-- string ^-- string },};To derive a reusable type for function signatures:
export type Env = ReturnType<typeof env.get>;Core Concepts
Section titled “Core Concepts”Two-Phase Lifecycle
Section titled “Two-Phase Lifecycle”The env instance has two methods: init() and get(). Calling init(rawEnv) validates every binding against the schema. If any fail, it throws an EnvError listing all failures at once so you can fix everything in a single pass. After a successful init(), subsequent calls are a no-op — the first valid environment wins.
get() returns the validated, fully typed object. It throws if init() has not been called successfully.
Binding Types
Section titled “Binding Types”Each binding declaration maps to a concrete Cloudflare type at compile time and uses duck-type validation at runtime:
| Type | TypeScript Type | Validation |
|---|---|---|
d1 | D1Database | Object with .prepare() method |
kv | KVNamespace | Object with .get() and .put() methods |
r2 | R2Bucket | Object with .put() and .head() methods |
queue | Queue | Object with .send() method |
durable-object | DurableObjectNamespace | Object with .get() and .idFromName() methods |
service | Fetcher | Object with .fetch() method |
secret | string | Non-empty string |
var | string | String (empty allowed, supports defaults) |
The secret type requires a non-empty string and does not allow defaults — it is intended for values set via wrangler secret put. The var type supports defaults and custom validation callbacks for configuration set in wrangler.toml.
Environment-Aware Defaults
Section titled “Environment-Aware Defaults”Variables can have different defaults per environment. The current environment is determined by a reserved ENVIRONMENT binding in rawEnv (valid values: "development", "staging", "production"):
const env = defineEnv({ APP_URL: { type: "var", default: { development: "http://localhost:8787", staging: "https://staging.myapp.com", production: "https://myapp.com", }, },});Set the environment in wrangler.toml:
[vars]ENVIRONMENT = "production"
[env.staging.vars]ENVIRONMENT = "staging"Common Patterns
Section titled “Common Patterns”Custom Validation on Variables
Section titled “Custom Validation on Variables”Use the validate callback to constrain variable values beyond simple type checks:
const env = defineEnv({ LOG_LEVEL: { type: "var", default: "info", validate: (v) => ["debug", "info", "warn", "error"].includes(v), }, APP_URL: { type: "var", default: "http://localhost:8787", validate: (v) => v.startsWith("http"), },});Error Handling at Startup
Section titled “Error Handling at Startup”EnvError collects all validation failures so you can address them together:
import { EnvError } from "@cfast/env";
try { env.init(rawEnv);} catch (e) { if (e instanceof EnvError) { console.error(e.message); // @cfast/env: 2 binding error(s): // - DB: Missing required D1 binding 'DB'. Check your wrangler.toml. // - API_KEY: Missing required secret 'API_KEY'. Check your wrangler.toml.
for (const err of e.errors) { console.error(err.key, err.message); } }}Passing Env to Other Packages
Section titled “Passing Env to Other Packages”Other cfast packages accept the validated env object directly. This is the primary integration point — @cfast/env feeds typed bindings into @cfast/db, @cfast/auth, and @cfast/storage:
env.init(rawEnv);const { DB } = env.get();const db = createDb({ d1: DB, schema, permissions, user });When using @cfast/core, the env schema is passed directly to createApp() and initialization is handled automatically via app.init(rawEnv).