@stitchem/config
v1.0.0
Published
Type-safe configuration management for Stitchem. Loads values from `.env` files, merges multiple sources, validates with your schema library of choice, and exposes everything through a fully-typed DI service.
Downloads
1,861
Readme
@stitchem/config
Type-safe configuration management for Stitchem. Loads values from .env files, merges multiple sources, validates with your schema library of choice, and exposes everything through a fully-typed DI service.
Install
npm install @stitchem/config
# or
pnpm add @stitchem/configPeer dependencies: @stitchem/core
Setup
Define named config namespaces, then load them in ConfigModule.forRoot:
import { ConfigModule, ConfigService, defineConfig, envSource } from '@stitchem/config';
import { module } from '@stitchem/core';
const databaseConfig = defineConfig('database', (storage) => ({
host: storage.get<string>('DB_HOST') ?? 'localhost',
port: Number(storage.get('DB_PORT') ?? 5432),
}));
@module({
imports: [
ConfigModule.forRoot({
sources: [envSource({ path: '.env' })],
load: [databaseConfig],
}),
],
})
class AppModule {}Augment the Registry
Extend ConfigRegistry to enable typed path access across your app:
// config.d.ts
declare module '@stitchem/config' {
interface ConfigRegistry {
database: { host: string; port: number };
}
}ConfigService.get() will now autocomplete and type-check all paths:
config.get('database') // { host: string; port: number }
config.get('database.host') // string
config.get('database.port') // numberSchema Validation
Pass any Standard Schema-compatible library as the third argument to defineConfig. Zod, Valibot, ArkType, and Effect Schema all work:
import { z } from 'zod';
const serverConfig = defineConfig(
'server',
(storage) => ({
host: storage.get('HOST'),
port: storage.get('PORT'),
}),
z.object({
host: z.string().default('0.0.0.0'),
port: z.coerce.number().min(1).max(65535).default(3000),
})
);Validation errors throw a ConfigError with structured issues.
Multiple Sources
Sources are deep-merged in order — later sources override earlier ones:
ConfigModule.forRoot({
sources: [
envSource({ path: '.env' }),
envSource({ path: '.env.local', required: false }),
],
load: [serverConfig, databaseConfig],
})Variable expansion (${VAR}) is enabled by default in EnvSource.
Feature Modules
Use forFeature to register config namespaces alongside the feature module that needs them:
const redisConfig = defineConfig('redis', (storage) => ({
url: storage.get<string>('REDIS_URL') ?? 'redis://localhost',
}));
@module({
imports: [ConfigModule.forFeature(redisConfig)],
providers: [CacheService],
})
class CacheModule {}Feature namespaces are initialized after the root module and become available via ConfigService.get('redis').
Accessing Config
Inject ConfigService anywhere in the DI container:
@injectable()
class AppService {
static inject = [ConfigService] as const;
constructor(private config: ConfigService) {}
start() {
const host = this.config.get('database.host');
const port = this.config.get('database.port');
console.log(`Connecting to ${host}:${port}`);
}
}API Reference
| Export | Description |
|--------|-------------|
| ConfigModule | DI module — forRoot(options) and forFeature(...configs) |
| ConfigService | Injectable — get(path), getNamespace(name), hasNamespace(name) |
| defineConfig(name, loader, schema?) | Create a typed namespace definition |
| EnvSource / envSource(options?) | Load values from a .env file |
| ConfigStorage | Internal injectable; receive it in namespace loaders |
| ConfigError | Error class with code and context fields |
| ConfigRegistry | Augmentable interface to type config paths |
| ConfigPath | Union of all valid dot-notation paths (from ConfigRegistry) |
| InferConfig<T> | Extract the output type of a ConfigNamespaceDefinition |
| ConfigSource | Interface for custom config sources |
| CONFIG_OPTIONS | Injection token for module options |
License
MIT
