@orijs/config
Technical spec for the configuration package. Source:
packages/config/src/
ConfigProvider Interface
Source: src/types.ts
The core contract for all configuration sources:
interface ConfigProvider {
get(key: string): Promise<string | undefined>;
getRequired(key: string): Promise<string>;
loadKeys(keys: string[]): Promise<Record<string, string | undefined>>;
}
| Method | Behavior |
|---|---|
get(key) | Returns the value or undefined if not found |
getRequired(key) | Throws if value is undefined or empty string |
loadKeys(keys) | Batch loads keys into a key-value record; used for eager caching at startup |
The async interface accommodates remote providers (e.g., Google Secrets Manager, Vault) alongside local environment variables.
EnvConfigProvider
Source: src/env-config.ts
Implements ConfigProvider by reading from Bun.env, which auto-loads:
- Shell environment variables
.env.local.env.{NODE_ENV}(e.g.,.env.development).env
class EnvConfigProvider implements ConfigProvider
All methods are thin wrappers over Bun.env[key]:
get()— returnsBun.env[key]getRequired()— throwsError('Required config '${key}' is not set...')when value isundefinedor''loadKeys()— iterates keys, reads each fromBun.env
This is the default provider for local development. For production, swap with a cloud provider or wrap with ValidatedConfig.
ValidatedConfig
Source: src/validated-config.ts
Decorator that wraps any ConfigProvider with validation, caching, and key access tracking.
class ValidatedConfig implements ConfigProvider {
constructor(provider: ConfigProvider, logger?: Logger)
}
Configuration API (Fluent Builder)
| Method | Signature | Description |
|---|---|---|
expectKeys() | (...keys: string[]): this | Declares keys that must be present. Can be called multiple times; keys accumulate in a Set. |
onFail() | (mode: 'error' | 'warn'): this | Sets fail mode. 'error' throws on missing keys. 'warn' logs and continues. Default: 'warn'. |
validate() | (): Promise<this> | Checks all expected keys, caches their values, logs results. Must be called before sync access. |
Validation Flow
validate() calls checkExpectedKeys() which:
- Iterates
expectedKeysset - Calls
provider.get(key)for each - Caches the value (including
undefined) in an internalMap<string, string | undefined> - Classifies as
missingif value isundefinedor''
Returns ConfigValidationResult:
interface ConfigValidationResult {
valid: boolean;
missing: string[];
present: string[];
}
Behavior based on failMode:
'error'— logs error message, throwsError('Missing required config keys: ...')'warn'— logs warning, continues execution
After validation, sets validated = true to enable sync access.
Access Methods
| Method | Async | Requirement | Behavior |
|---|---|---|---|
get(key) | Yes | None | Delegates to wrapped provider, tracks key access |
getRequired(key) | Yes | None | Delegates to wrapped provider (always throws for missing) |
getSync(key) | No | validate() called, key in expectKeys() | Returns from cache. Throws if preconditions not met. |
getRequiredSync(key) | No | validate() called, key in expectKeys() | Returns from cache. Throws if missing/empty. |
getSync() and getRequiredSync() throw specific errors:
'Cannot use getSync() before validate() is called'— validation not run'Key "${key}" was not in expectedKeys...'— key not declared for caching
Key Access Tracking
Every get, getRequired, getSync, and getRequiredSync call registers the key in a loadedKeys set. Tracking methods:
getLoadedKeys(): string[]— returns all accessed keyslogLoadedKeys(): void— logs accessed key summary via the Logger instance
NamespacedConfigBuilder
Source: src/namespaced-config.ts
Multi-provider configuration system with namespace isolation and sync access after validation.
Factory
function createConfigProvider(logger?: Logger): NamespacedConfigBuilder
Builder API
| Method | Signature | Description |
|---|---|---|
add() | (namespace: string, provider: ConfigProviderInput): this | Registers a provider under a namespace. Throws if namespace === 'env' (reserved). |
expectKeys() | (keys: Record<string, string[]>): this | Declares required keys per namespace. Replaces previous declaration. |
onFail() | (mode: 'error' | 'warn'): this | Sets fail mode. Default: 'error'. |
transform() | (transformer: ConfigTransformer): this | Adds a post-validation transformer. Applied in registration order. |
validate<T>() | (): Promise<T & ConfigProvider> | Validates, caches, applies transformers, returns typed Proxy. |
ConfigProviderInput
Providers can be passed in three forms, resolved during validate():
type ConfigProviderInput = ConfigProvider | ConfigProviderConstructor | ConfigProviderFactory;
type ConfigProviderConstructor = new () => ConfigProvider;
interface ConfigProviderFactory {
create(): Promise<ConfigProvider>;
}
Resolution order:
- If
isConfigProviderFactory()— hascreate()method: callsawait input.create() - If
isConfigProviderConstructor()— is a function: callsnew input() - Otherwise: used as-is (already a
ConfigProviderinstance)
The env Namespace
Always available without registration. Reads directly from Bun.env during validation (does not use a provider instance). Cannot be overridden — add('env', ...) throws:
Error: Cannot override "env" namespace - it is reserved for environment variables
Validation Flow
- Load
envnamespace keys fromBun.env - Verify all namespaces in
expectKeyshave been registered viaadd(); throws if not - For each registered provider: resolve provider instance, call
provider.loadKeys(keys) - Classify keys as
missing(undefined or empty) orpresent - Handle validation result per
failMode - Apply transformers in order
Transformers
interface ConfigTransformer<TInput = unknown, TOutput = unknown> {
readonly property: string;
readonly transform: (config: TInput) => TOutput;
}
Transformers derive new properties from the validated config object. They receive the full config (all namespaces + previously applied transformers) and their return value is set on result[transformer.property].
Return Value
validate<T>() returns a Proxy that implements both the typed config interface T and ConfigProvider:
- Property access (
config.env.PORT,config.secrets.KEY) — reads from the cached namespace objects config.get(key)— searches all namespaces,secretsfirst, then othersconfig.getRequired(key)— throws if not found in any namespaceconfig.loadKeys(keys)— delegates toget()per key- Unknown namespace access returns
{}(any property access on it returnsundefined)
NamespacedConfigResult
Default type when T is not specified:
type NamespaceAccessor = Record<string, string | undefined>;
type NamespacedConfigResult = {
env: NamespaceAccessor;
[namespace: string]: NamespaceAccessor;
};