One value, every runtime.
A from-scratch design for atomic configuration: each value is an inert, validated descriptor, bound explicitly to whichever runtime reads it, secret by default. It’s for teams running one codebase across Node, Cloudflare Workers and the browser who’ve outgrown a single import-time-validated config object.
21
documented hard constraints
11
accepted architecture decisions
// config/db.ts — inert tesserae. Importing this file costs bytes, not behavior.import { tessera } from "tessellum";import { z } from "zod"; export const dbUrl = tessera({ key: "DATABASE_URL", schema: z.string().url(), doc: "Primary Postgres connection string", // exposure defaults to "secret" — fail closed});export const poolSize = tessera({ key: "DB_POOL_SIZE", schema: z.coerce.number().int().min(1).max(100), default: 10, exposure: "public",});At a glance
- Type
- My own open-source library
- Scope
- Everything: the constitution (21 hard constraints), 11 accepted decision records, and the documentation site — no library code has shipped yet.
- Status
- Design phase — no code yet · as of 24 Sep 2026
- Timeline
Started Jul 2026 · not released yet
- Platforms
- Designed for Node.js, Cloudflare Workers, the browser
- Source code
- Source on GitHub (opens in a new tab)
- Try it
- No code yet — the design documents are the project for now.
- Stack
The challenge
Most configuration libraries — t3-env, envalid, znv — load and validate every declared value in one import-time call, even when a given entry point reads only one of them. That works for a conventional server, but it can’t bind to a Cloudflare Worker’s per-request environment, has nothing to say about browsers, and treats every value, secret or not, as an interchangeable string.
- Boot-crash coupling: an unrelated, unset variable can crash a service at startup.
- Runtime lock-in: import-time validation can’t see a Worker’s per-request environment.
- Undocumented strings: a raw environment-variable read has no type, docs or exposure rule.
- One monolithic call: existing libraries validate everything at once, so nothing binds per runtime.
The constraints
The design has to behave the same way on a Node server, inside a Cloudflare Worker and in the browser, without smuggling Node-only code into a client bundle or leaking a secret by accident.
- No Node APIs outside dedicated entry points — the core must run in a browser.
- Zero runtime dependencies, with size budgets enforced in CI.
- Secret by default — public is an explicit, deliberate opt-in.
- No background timers, watchers or polling loops.
- Nothing changes mid-request unless a value is explicitly declared live.
The solution
I modelled configuration as small, inert, content-addressed descriptors instead of one big validated object, so the same declaration binds safely to a Node process, a Worker’s per-request environment, a baked browser bundle, or a test literal. I wrote every hard rule — inert by construction, secret by default, no ambient state — down as one of 21 gates a feature has to pass, and recorded each contested choice as its own decision record, rejected alternatives included.
Inheritance is declared data, never a function
A value that inherits another — a per-service log level falling back to a global default — sometimes needs to transform it first. A function can’t be hashed or safely run at build time, so the transform is a small, fixed set of data operations instead.
Secrets are a Proxy, not a plain string
A spike proved a plain or boxed string can’t stop a secret leaking through structured cloning or an object spread. A Proxy blocks every one of those paths while still behaving like an ordinary string everywhere it’s used.
Bake ships handles, never the real config file
An early version had client code import the real config declarations, which silently shipped an entire validation library to the browser — about 166 times the size. Bake now emits a lightweight stand-in with no validator attached instead.
An oversized atomic write is refused, never split
Splitting one multi-value update across several database transactions means a crash between them can leave the database itself half-updated. Past the store’s own transaction limit, the write is refused outright before anything is sent.
Related config values are grouped on purpose, not by default
The library can’t know on its own which values — a database host and its password — must never be read out of sync with each other. Related values are grouped explicitly, so only a group pays the cost of being re-read together.
Architecture
Tessera
Inert, validated descriptor
Node
Validates environment at boot
Cloudflare Workers
Binds the per-request environment
Browser (bake)
Inlines literals, no secrets
Tests
Literal values, no env mutation
Secret by default
Public is an explicit opt-in
One contract
Same descriptor, every runtime
I build — you own
Built to hand over
Tessellum hasn’t shipped code yet, but the design is public and finished enough to hand over — every item below is real, on GitHub today.
- View (opens in a new tab)
Constitution
principles.md — 21 hard constraints (P1–P21), the security model and the non-goals, each pitfall found through adversarial review.
- View (opens in a new tab)
Decision records
11 accepted ADRs, each recording the context, the rejected options and what the decision costs — start with ADR-0001.
- View (opens in a new tab)
Documentation site
13 concept and guide pages, written as if the library had already shipped, plus a full API and decisions reference.
Results
Nothing has shipped as code yet: the design is the deliverable. What exists is the constitution, 11 accepted decision records, a documentation site written as if the library had already shipped, and a CI pipeline — already green — testing the placeholder package on Node 22 and 24.
13
concept and guide pages
Apache-2.0
open-source licence
What’s next
The next milestone is v0.1: one tesserae file consumed unchanged by a Node server, a Cloudflare Worker and a Vite client — with bake failing the build on a planted secret leak — plus a Vitest suite that never mutates process.env.
Have a similar project in mind?
I’ll build it and hand it over: the code, the docs and the accounts are yours.
Discuss a project