or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-features.mdconfiguration.mddata-operations.mdindex.md
tile.json

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