@clipboard-health/config
Type-safe static configuration management: a pure function to resolve, validate against a Zod
schema, and freeze configuration values.
Configuration values resolve in order from highest precedence to lowest:
- Environment variables
- Resolved converting configuration path from camelCase to UPPER_SNAKE. For example, the
{ myApi: { port: 3000 } }
configuration resolves to MY_API_PORT
.
- Environment-specific overrides, {@link ConfigValue.overrides}
- Default values, {@link ConfigValue.defaultValue}
Supported configuration value types:
- bigint
- boolean
- date
- number
- string
- arrays and nested objects using the above types
To override arrays with environment variables, use stringified JSON arrays, e.g. ["a","b"]
.
IMPORTANT: To avoid runtime errors:
- Environment variables are strings, so use
z.coerce
Zod types for those you plan to override.
Note that z.coerce.boolean()
coerces any truthy value to true
. To restrict to "true" | "false"
, use the booleanString
schema from @clipboard-health/contract-core
. - The resulting configuration is deeply frozen and will throw a runtime error if you attempt to
modify it. The actual return type is
ReadonlyDeep<SchemaT>
, but the library returns a
Readonly<SchemaT>
because the former prevents clients from passing configuration values to
functions that don't explicitly accept readonly
types.
Table of contents
Install
npm install @clipboard-health/config
Usage
Type-safe configuration
import { ok } from "node:assert/strict";
import { createConfig } from "@clipboard-health/config";
import { z } from "zod";
const allowed = ["local", "development", "production"] as const;
type Allowed = (typeof allowed)[number];
function createEnvironmentConfig(current: Allowed) {
return createConfig({
config: {
baseUrl: {
defaultValue: "http://localhost:3000",
description: "Base URL for API requests",
overrides: {
development: "https://dev.example.com",
production: "https://api.example.com",
},
},
database: {
port: {
defaultValue: 5432,
description: "Database port",
},
},
},
environment: { allowed, current },
schema: z.object({
baseUrl: z.string().url(),
database: z.object({
port: z.coerce.number().min(1024).max(65_535),
}),
}),
});
}
{
const config = createEnvironmentConfig("local");
ok(config.baseUrl === "http://localhost:3000");
ok(config.database.port === 5432);
}
{
const config = createEnvironmentConfig("development");
ok(config.baseUrl === "https://dev.example.com");
ok(config.database.port === 5432);
}
const original = { ...process.env };
try {
process.env["BASE_URL"] = "https://staging.example.com";
process.env["DATABASE_PORT"] = "54320";
const config = createEnvironmentConfig("local");
ok(config.baseUrl === "https://staging.example.com");
ok(config.database.port === 54_320);
} finally {
process.env = { ...original };
}
Local development commands
See package.json
scripts
for a list of commands.