The ObjectSource layers a plain JavaScript object into your configuration. It is available from both the server and the client entry points, and it is the right choice when you want to avoid files entirely, override a value while debugging, or build a config in a test. It also supports slots, allowing you to use environment variables:
import { ConfigBuilder, ObjectSource } from "@layerfig/config/client";import { schema } from "./schema";export const config = new ConfigBuilder({ validate: (finalConfig) => schema.parse(finalConfig),}) .addSource( new ObjectSource({ baseURL: "${PUBLIC_BASE_URL}", randomValue: true, }) ) .build();
Since a framework like Vite adds PUBLIC_* environment variables to import.meta.env and makes them available on the client, your config object will contain all the type-checked values.
Your application’s configuration files are committed to version control and used to build a Docker image for production. When running this image locally for debugging, any change to a configuration value requires rebuilding the image. This process can be slow and results in testing a modified image rather than the actual production build.
To avoid this, use EnvironmentVariableSource to override configuration values at runtime without modifying the source files:
import { ConfigBuilder, FileSource, EnvironmentVariableSource,} from "@layerfig/config";import { schema } from "./schema";export const config = new ConfigBuilder({ validate: (finalConfig) => schema.parse(finalConfig),}) .addSource(new FileSource("base.json")) .addSource(new EnvironmentVariableSource()) .build();
By default, the library expects environment variables with the following structure:
Prefix: "APP"
Prefix separator: "_"
Nested key separator: "__"
For example, the environment variable APP_port overrides the port key in your configuration.
port above is declared as z.coerce.number() rather than z.number() on purpose. base.json provides the number 4444, but APP_port=8080 provides the string "8080", and the schema has to accept both.
The same applies to booleans, with one extra trap:
Sources are merged in the order they are added, and each one is layered on top of the result so far. Objects are merged deeply, so a later source only has to declare the keys it changes: