CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-conf

Simple config handling for your app or module

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced functionality for production applications including change monitoring, file watching, migration systems, and custom serialization formats.

Capabilities

Change Monitoring

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();

File Watching

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 automatically

Event System

Low-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);

Migration System

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 projectVersion

Custom Serialization

Support 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
});

Encryption and Obfuscation

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"

Error Handling

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"
}

docs

advanced-features.md

configuration.md

data-operations.md

index.md

tile.json