rhizome-node/docs/custom-resolvers/api/resolver-plugin-interface.md
Lentil Hoffman d7c4fda93e
refactor(resolver): overhaul plugin system and dependency handling
Core Changes:
- Completely rewrote CustomResolver reducer with dependency-ordered processing
- Enhanced plugin initialization with proper dependency injection
- Improved delta processing and property value tracking
- Added robust error handling for duplicate property IDs

Resolver Improvements:
- Updated to use new accumulator structure
- Implemented execution order processing for plugins
- Enhanced debug logging and error reporting
- Simplified TimestampResolver by removing unused initializer

Configuration Updates:
- Added TypeScript path aliases for test helpers
- Improved module resolution paths

Key Benefits:
- More robust plugin dependency management
- More efficient state updates
- Enhanced type safety
- Better error messages and debugging
- More consistent plugin initialization

This refactoring focuses on improving the robustness of the resolver,
especially around plugin lifecycle management and dependency handling.
The changes ensure better separation of concerns and more predictable
behavior when dealing with complex plugin dependencies.
2025-06-25 06:10:34 -05:00

4.2 KiB

ResolverPlugin Interface

Overview

The ResolverPlugin interface defines the contract that all resolver plugins must implement. It provides type-safe access to plugin state and dependencies.

Interface Definition

interface ResolverPlugin<T = unknown, D extends string = never> {
  /**
   * Unique identifier for the plugin
   */
  readonly name: string;

  /**
   * Array of property IDs this plugin depends on
   * @default []
   */
  readonly dependencies?: readonly D[];

  /**
   * Initializes the plugin's state
   * @returns Initial state object
   */
  initialize(): T;

  /**
   * Processes a new value and updates the plugin's state
   * @param currentState Current plugin state
   * @param newValue New value to process
   * @param delta Delta information
   * @param dependencies Resolved states of all declared dependencies
   * @returns Updated plugin state
   */
  update(
    currentState: T,
    newValue: PropertyTypes,
    delta: CollapsedDelta,
    dependencies: DependencyStates
  ): T;

  /**
   * Resolves the final value from the current state
   * @param state Current plugin state
   * @param dependencies Resolved states of all declared dependencies
   * @returns Resolved value or undefined if no value should be set
   */
  resolve(
    state: T,
    dependencies: DependencyStates
  ): PropertyTypes | undefined;
}

Type Parameters

Parameter Description
T Type of the plugin's internal state
D Union type of dependency names (must extend string)

Methods

initialize()

Initializes the plugin's internal state. Called once when the resolver is created.

Returns: T - The initial state object

update(currentState, newValue, delta, dependencies)

Processes a new value and updates the plugin's state.

Parameters:

  • currentState: T - Current plugin state
  • newValue: PropertyTypes - New value to process
  • delta: CollapsedDelta - Delta information
  • dependencies: DependencyStates - Resolved states of all declared dependencies

Returns: T - Updated plugin state

resolve(state, dependencies)

Resolves the final value from the current state.

Parameters:

  • state: T - Current plugin state
  • dependencies: DependencyStates - Resolved states of all declared dependencies

Returns: PropertyTypes | undefined - Resolved value or undefined if no value should be set

Example Implementation

class CounterPlugin implements ResolverPlugin<CounterState> {
  
  initialize(): CounterState {
    return { count: 0 };
  }
  
  update(
    state: CounterState,
    _newValue: unknown,
    _delta: CollapsedDelta,
    _deps: {}
  ): CounterState {
    return { count: state.count + 1 };
  }
  
  resolve(state: CounterState): number {
    return state.count;
  }
}

Best Practices

  1. Immutability: Always return new state objects instead of mutating
  2. Purity: Keep methods pure and side-effect free
  3. Error Handling: Handle unexpected input gracefully
  4. Documentation: Document expected types and behavior

Common Patterns

Accessing Dependencies

class PriceCalculator implements ResolverPlugin<PriceState, 'basePrice' | 'taxRate'> {
  readonly dependencies = ['basePrice', 'taxRate'] as const;
  
  update(
    _state: PriceState,
    _newValue: unknown,
    _delta: CollapsedDelta,
    deps: DependencyStates,
  ): PriceState {
    const basePrice = deps.basePrice as number;
    const taxRate = deps.taxRate as number;
    return { total: basePrice * (1 + taxRate) };
  }
  
  // ...
}

Optional Dependencies

class OptionalDepPlugin implements ResolverPlugin<State, 'required' | 'optional?'> {
  readonly dependencies = ['required', 'optional?'] as const;
  
  update(
    state: State,
    _newValue: unknown,
    _delta: CollapsedDelta,
    deps: any,
  ): State {
    const required = deps.required as number; // Always present
    const optional = deps['optional?'] as number | undefined; // Might be undefined
    
    // ...
  }
  
  // ...
}