CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-unified

Parse, inspect, transform, and serialize content through syntax trees

Pending
Overview
Eval results
Files

configuration-management.mddocs/

Configuration Management

Processor configuration, data storage, and state management system with freezing capabilities for controlling processor modifications and sharing data across plugins.

Capabilities

Data Method

Manages processor-specific data storage accessible to all plugins.

/**
 * Get or set processor data
 * @returns Complete data object when called with no arguments
 */
data(): Data;

/**
 * Get specific data value
 * @param key - Data key to retrieve
 * @returns Value associated with the key
 */
data<Key extends keyof Data>(key: Key): Data[Key];

/**
 * Set specific data key-value pair
 * @param key - Data key to set
 * @param value - Value to associate with key
 * @returns Processor instance for chaining
 */
data<Key extends keyof Data>(key: Key, value: Data[Key]): Processor;

/**
 * Replace entire data object
 * @param dataset - New data object to set
 * @returns Processor instance for chaining
 */
data(dataset: Data): Processor;

Usage Examples:

import { unified } from "unified";

const processor = unified();

// Get all data
const allData = processor.data();
console.log(allData); // {}

// Set individual key-value pairs
processor
  .data("author", "John Doe")
  .data("version", "1.0.0")
  .data("config", { strict: true });

// Get specific values
const author = processor.data("author");    // "John Doe"
const config = processor.data("config");    // { strict: true }

// Set entire data object
processor.data({
  title: "My Document",
  settings: { format: "html" },
  metadata: { created: new Date() }
});

// Get complete data object
const data = processor.data();
// { title: "My Document", settings: {...}, metadata: {...} }

Settings Management

Special data handling for parser and compiler settings via the settings key.

// Set settings via data method
processor.data("settings", {
  bullet: "*",
  emphasis: "_",
  strong: "*"
});

// Access settings
const settings = processor.data("settings");

// Merge additional settings
const existingSettings = processor.data("settings") || {};
processor.data("settings", {
  ...existingSettings,
  newSetting: "value"
});

Data in Plugins

Plugins can access and modify processor data for cross-plugin communication.

function dataAwarePlugin(options = {}) {
  // Access data during plugin setup
  const existingConfig = this.data("config") || {};
  
  // Merge plugin options with existing config
  this.data("config", { ...existingConfig, ...options });
  
  // Set plugin-specific data
  this.data("pluginState", { initialized: true });
  
  return function transformer(tree, file) {
    // Access data during transformation
    const config = this.data("config");
    const pluginState = this.data("pluginState");
    
    // Use data to guide transformation
    if (config.strict && !pluginState.validated) {
      // Perform strict validation
    }
  };
}

// Usage
processor
  .use(dataAwarePlugin, { strict: true })
  .use(anotherPlugin);

Cross-Plugin Data Sharing

Share data between plugins for coordinated behavior.

// First plugin sets shared data
function configPlugin(options) {
  this.data("sharedConfig", {
    theme: options.theme || "default",
    features: options.features || []
  });
}

// Second plugin uses shared data
function themedPlugin() {
  return function transformer(tree, file) {
    const config = this.data("sharedConfig");
    
    if (config && config.theme === "dark") {
      // Apply dark theme transformations
    }
    
    if (config && config.features.includes("syntax-highlight")) {
      // Enable syntax highlighting
    }
  };
}

// Usage
processor
  .use(configPlugin, { 
    theme: "dark", 
    features: ["syntax-highlight", "line-numbers"] 
  })
  .use(themedPlugin);

Processor State Management

Freeze Method

Freezes processor configuration to prevent further modifications.

/**
 * Freeze processor to prevent configuration changes
 * @returns The frozen processor instance
 */
freeze(): Processor;

Usage Examples:

const processor = unified()
  .use(someParser)
  .use(someTransformer);

// Freeze the processor
const frozenProcessor = processor.freeze();

// Attempt to modify frozen processor (throws error)
try {
  frozenProcessor.use(anotherPlugin);
} catch (error) {
  console.log(error.message); 
  // "Cannot call `use` on a frozen processor..."
}

// Reading operations still work
const data = frozenProcessor.data();        // ✓ Works
const result = frozenProcessor.processSync("content"); // ✓ Works

Processor Copying

Create new unfrozen processors from existing configurations.

const baseProcessor = unified()
  .use(commonParser)
  .use(baseTransformer)
  .freeze();

// Create variants without affecting base
const htmlProcessor = baseProcessor()  // Call as function
  .use(htmlCompiler);

const textProcessor = baseProcessor()
  .use(textCompiler);

// Base processor remains unchanged
console.log(baseProcessor.frozen);     // true
console.log(htmlProcessor.frozen);     // false
console.log(textProcessor.frozen);     // false

Automatic Freezing

Processors automatically freeze when processing methods are called.

const processor = unified()
  .use(someParser)
  .use(someTransformer);

console.log(processor.frozen); // false

// First processing call freezes the processor
const result = processor.parse("content");

console.log(processor.frozen); // true

// Subsequent modification attempts fail
try {
  processor.use(anotherPlugin); // Throws error
} catch (error) {
  console.log("Processor is now frozen");
}

Advanced Configuration Patterns

Environment-Based Configuration

Configure processors based on runtime environment.

function createProcessor(env = "production") {
  const processor = unified().use(baseParser);
  
  // Environment-specific data
  processor.data("environment", env);
  processor.data("debug", env === "development");
  
  if (env === "development") {
    processor
      .use(debugPlugin)
      .use(validationPlugin, { strict: true });
  } else {
    processor
      .use(optimizationPlugin)
      .use(minificationPlugin);
  }
  
  return processor;
}

// Usage
const devProcessor = createProcessor("development");
const prodProcessor = createProcessor("production");

Configuration Validation

Validate processor configuration before processing.

function validateConfig() {
  return function validator(tree, file) {
    const data = this.data();
    
    // Check required configuration
    if (!data.settings) {
      throw new Error("Settings are required");
    }
    
    if (!data.settings.format) {
      throw new Error("Output format must be specified");
    }
    
    // Validate configuration values
    const validFormats = ["html", "markdown", "text"];
    if (!validFormats.includes(data.settings.format)) {
      throw new Error(`Invalid format: ${data.settings.format}`);
    }
  };
}

// Usage
processor
  .use(configPlugin, { format: "html" })
  .use(validateConfig)
  .use(outputPlugin);

Plugin State Management

Manage plugin-specific state across multiple transformations.

function statefulPlugin() {
  // Initialize plugin state
  this.data("pluginState", {
    processedCount: 0,
    cache: new Map(),
    initialized: Date.now()
  });
  
  return function transformer(tree, file) {
    const state = this.data("pluginState");
    
    // Update state
    state.processedCount++;
    
    // Use cached results
    const cacheKey = JSON.stringify(tree);
    if (state.cache.has(cacheKey)) {
      return state.cache.get(cacheKey);
    }
    
    // Process and cache result
    const result = processTree(tree);
    state.cache.set(cacheKey, result);
    
    return result;
  };
}

Error Handling

Configuration Errors

  • Frozen processor modifications: "Cannot call \data` on a frozen processor"`
  • Invalid data operations: Type errors for incorrect key/value types

Data Access Patterns

  • Undefined keys return undefined
  • No prototypal property access (uses hasOwnProperty)
  • Functions can be stored as data values

State Consistency

  • Data changes are immediate and visible to all subsequent plugins
  • Freezing prevents accidental configuration changes during processing
  • Copying preserves data but creates new mutable instances

Install with Tessl CLI

npx tessl i tessl/npm-unified

docs

configuration-management.md

index.md

plugin-system.md

processing-pipeline.md

tile.json