Advanced functionality for production applications including change monitoring, file watching, migration systems, and custom serialization formats.
Event-driven system for monitoring configuration changes with granular control over what changes to track.
/**
* Watch specific key for changes
* @param key - The key to watch for changes
* @param callback - Function called when key changes
* @returns Unsubscribe function to stop watching
*/
onDidChange<Key extends keyof T>(key: Key, callback: OnDidChangeCallback<T[Key]>): Unsubscribe;
/**
* Watch specific nested property for changes via dot notation
* @param key - Dot notation key to watch
* @param callback - Function called when nested property changes
* @returns Unsubscribe function to stop watching
*/
onDidChange<Key extends DotNotationKeyOf<T>>(key: Key, callback: OnDidChangeCallback<DotNotationValueOf<T, Key>>): Unsubscribe;
/**
* Watch any key for changes (generic version)
* @param key - Any string key to watch
* @param callback - Function called when key changes
* @returns Unsubscribe function to stop watching
*/
onDidChange<Key extends string>(key: Key, callback: OnDidChangeCallback<any>): Unsubscribe;
/**
* Watch entire configuration for any changes
* @param callback - Function called when any configuration changes
* @returns Unsubscribe function to stop watching
*/
onDidAnyChange(callback: OnDidAnyChangeCallback<T>): Unsubscribe;
type OnDidChangeCallback<T> = (newValue?: T, oldValue?: T) => void;
type OnDidAnyChangeCallback<T> = (newValue?: Readonly<T>, oldValue?: Readonly<T>) => void;
type Unsubscribe = () => void;Usage Examples:
interface AppConfig {
username: string;
preferences: {
theme: "light" | "dark";
notifications: boolean;
};
}
const config = new Conf<AppConfig>({ projectName: "my-app" });
// Watch specific property changes
const unsubscribeUsername = config.onDidChange("username", (newValue, oldValue) => {
console.log(`Username changed from "${oldValue}" to "${newValue}"`);
});
// Watch nested property changes
const unsubscribeTheme = config.onDidChange("preferences.theme", (newValue, oldValue) => {
console.log(`Theme changed from "${oldValue}" to "${newValue}"`);
updateUITheme(newValue);
});
// Watch for any configuration changes
const unsubscribeAll = config.onDidAnyChange((newConfig, oldConfig) => {
console.log("Configuration changed:");
console.log("Old config:", oldConfig);
console.log("New config:", newConfig);
// Save backup or sync to server
saveConfigBackup(newConfig);
});
// Trigger changes to see events
config.set("username", "john"); // Triggers username watcher
config.set("preferences.theme", "dark"); // Triggers theme watcher and any-change watcher
// Clean up watchers when done
unsubscribeUsername();
unsubscribeTheme();
unsubscribeAll();Monitor the configuration file for external changes when multiple processes access the same configuration.
// Enable file watching via constructor option
interface WatchOptions {
/**
* Watch config file for external changes.
* Useful when multiple processes modify the same file.
* @default false
*/
watch?: boolean;
}Usage Examples:
// Enable file watching for multi-process scenarios
const config = new Conf({
projectName: "multi-process-app",
watch: true
});
// Set up change handlers for external modifications
config.onDidAnyChange((newValue, oldValue) => {
console.log("Config file was modified externally");
// Handle external changes
if (newValue?.username !== oldValue?.username) {
console.log("Username was changed by another process");
updateUI(newValue.username);
}
});
// Another process or manual edit changes the file
// The change handler will be triggered automaticallyLow-level event system for advanced use cases and custom integrations.
/**
* EventTarget for listening to configuration changes
*/
readonly events: EventTarget;Usage Examples:
// Access low-level event system
config.events.addEventListener("change", () => {
console.log("Configuration changed (low-level event)");
});
// Custom event handling with more control
config.events.addEventListener("change", (event) => {
console.log("Event type:", event.type);
console.log("Current config:", config.store);
// Custom logic for different types of changes
handleConfigurationChange();
});
// Remove event listeners
const handler = () => console.log("Config changed");
config.events.addEventListener("change", handler);
config.events.removeEventListener("change", handler);Powerful version-based migration system for handling configuration schema changes across application versions.
interface MigrationOptions<T> {
/**
* Current project version - required for migrations
*/
projectVersion?: string;
/**
* Migration handlers keyed by version or semver range
*/
migrations?: Migrations<T>;
/**
* Callback executed before each migration step
*/
beforeEachMigration?: BeforeEachMigrationCallback<T>;
}
type Migrations<T> = Record<string, (store: Conf<T>) => void>;
type BeforeEachMigrationCallback<T> = (store: Conf<T>, context: BeforeEachMigrationContext) => void;
interface BeforeEachMigrationContext {
fromVersion: string;
toVersion: string;
finalVersion: string;
versions: string[];
}Usage Examples:
// Define migration configuration
const config = new Conf({
projectName: "evolving-app",
projectVersion: "3.1.0", // Current app version
// Log migration progress
beforeEachMigration: (store, context) => {
console.log(`Migrating config from ${context.fromVersion} to ${context.toVersion}`);
console.log(`Final target version: ${context.finalVersion}`);
console.log(`All migration versions: ${context.versions.join(", ")}`);
// Backup before each migration
const backup = JSON.stringify(store.store);
fs.writeFileSync(`config-backup-${context.fromVersion}.json`, backup);
},
migrations: {
// Exact version migrations
"1.0.0": (store) => {
console.log("Migrating to v1.0.0");
store.set("version", "1.0.0");
store.set("migrated", true);
},
"2.0.0": (store) => {
console.log("Migrating to v2.0.0 - restructuring preferences");
// Move old settings to new structure
const oldTheme = store.get("theme");
const oldLang = store.get("language");
if (oldTheme || oldLang) {
store.set("ui", {
theme: oldTheme || "light",
language: oldLang || "en"
});
// Clean up old settings
store.delete("theme");
store.delete("language");
}
},
"2.5.0": (store) => {
// Add new features introduced in v2.5.0
store.set("features", {
notifications: true,
autoSave: true,
cloudSync: false
});
},
// Semver range migrations
">=3.0.0": (store) => {
console.log("Migrating to v3.x.x - major restructure");
// Major version changes
const oldConfig = store.store;
// Transform to new schema
store.clear();
store.set("schema_version", 3);
store.set("settings", {
user: oldConfig.user || {},
ui: oldConfig.ui || {},
features: oldConfig.features || {}
});
},
"3.1.0": (store) => {
// Minor update in v3.1.0
const settings = store.get("settings");
if (settings?.features) {
store.set("settings.features.beta", false);
}
}
}
});
// Migrations run automatically when the Conf instance is created
// Based on the stored version vs projectVersionSupport for alternative serialization formats beyond JSON.
interface SerializationOptions<T> {
/**
* Custom serialization function
* @default value => JSON.stringify(value, null, '\t')
*/
serialize?: Serialize<T>;
/**
* Custom deserialization function
* @default JSON.parse
*/
deserialize?: Deserialize<T>;
/**
* Custom file extension for serialized format
* @default 'json'
*/
fileExtension?: string;
}
type Serialize<T> = (value: T) => string;
type Deserialize<T> = (text: string) => T;Usage Examples:
import yaml from "js-yaml";
import toml from "@iarna/toml";
// YAML configuration format
const yamlConfig = new Conf({
projectName: "yaml-app",
fileExtension: "yaml",
serialize: (value) => yaml.dump(value, {
indent: 2,
lineWidth: 120
}),
deserialize: (text) => yaml.load(text) as any
});
// TOML configuration format
const tomlConfig = new Conf({
projectName: "toml-app",
fileExtension: "toml",
serialize: (value) => toml.stringify(value),
deserialize: (text) => toml.parse(text) as any
});
// Custom format with compression
import { gzipSync, gunzipSync } from "zlib";
const compressedConfig = new Conf({
projectName: "compressed-app",
fileExtension: "gz",
serialize: (value) => {
const json = JSON.stringify(value);
const compressed = gzipSync(json);
return compressed.toString("base64");
},
deserialize: (text) => {
const compressed = Buffer.from(text, "base64");
const json = gunzipSync(compressed).toString();
return JSON.parse(json);
}
});
// Pretty-printed JSON with custom formatting
const prettyConfig = new Conf({
projectName: "pretty-app",
serialize: (value) => JSON.stringify(value, null, 2),
deserialize: JSON.parse
});Built-in encryption capabilities for configuration file obfuscation.
interface EncryptionOptions {
/**
* Encryption key for file obfuscation (not intended for security).
* Helps deter manual editing of config files.
*/
encryptionKey?: string | Uint8Array | NodeJS.TypedArray | DataView;
}Usage Examples:
import { randomBytes } from "crypto";
// String-based encryption key
const encryptedConfig = new Conf({
projectName: "secure-app",
encryptionKey: "my-secret-obfuscation-key"
});
// Binary encryption key
const binaryKey = randomBytes(32);
const binaryEncryptedConfig = new Conf({
projectName: "binary-secure-app",
encryptionKey: binaryKey
});
// Uint8Array key
const uint8Key = new Uint8Array(32);
crypto.getRandomValues(uint8Key);
const uint8EncryptedConfig = new Conf({
projectName: "uint8-secure-app",
encryptionKey: uint8Key
});
// The config file will be encrypted with AES-256-CBC
// If the file is tampered with, it will reset to defaults
encryptedConfig.set("sensitive", "data");
console.log("Config file is encrypted on disk");
// Reading and writing works normally in your code
console.log(encryptedConfig.get("sensitive")); // => "data"Configuration for handling various error conditions.
interface ErrorHandlingOptions {
/**
* Clear config if reading causes SyntaxError.
* Good for recoverable corruption scenarios.
* @default false
*/
clearInvalidConfig?: boolean;
}Usage Examples:
// Robust config that recovers from corruption
const robustConfig = new Conf({
projectName: "robust-app",
clearInvalidConfig: true,
defaults: {
username: "guest",
theme: "light"
}
});
// If config file gets corrupted, it will reset to defaults
// instead of throwing an error
// Manual error handling for validation
try {
config.set("invalid-key", {
func: () => {} // Functions can't be serialized to JSON
});
} catch (error) {
console.error("Validation error:", error.message);
// Handle the error appropriately
}
// Schema validation errors
const schemaConfig = new Conf({
projectName: "validated-app",
schema: {
count: { type: "number", minimum: 0 }
}
});
try {
schemaConfig.set("count", -5);
} catch (error) {
console.error("Schema validation failed:", error.message);
// => "Config schema violation: `count` should be >= 0"
}