CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-memoizee

Complete memoization/caching solution for JavaScript functions with support for any argument types, async/promise functions, cache expiration, and advanced cache management features

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

method-memoization.mddocs/

Method Memoization

Specialized utilities for memoizing object methods with lazy property descriptors and proper this context handling. Ideal for prototype method optimization and instance-specific caching where methods need to be memoized on a per-instance basis.

Capabilities

Method Memoization with Property Descriptors

Create lazy property descriptors for memoizing object methods with proper this binding and instance-specific caching.

/**
 * Create memoized method property descriptors
 * @param {Object} methods - Object mapping method names to descriptors
 * @returns {Object} Lazy property descriptors for Object.defineProperties
 */
const memoizeMethods = require("memoizee/methods");
const descriptors = memoizeMethods(methodsObject);

Usage Examples:

const memoizeMethods = require("memoizee/methods");
const d = require("d");  // Property descriptor helper

class DataProcessor {
  constructor(data) {
    this.data = data;
    this.processed = false;
  }
}

// Define memoized methods on prototype
Object.defineProperties(DataProcessor.prototype, memoizeMethods({
  
  // Basic memoized method
  calculateSum: d(function() {
    console.log("Computing sum...");
    return this.data.reduce((sum, val) => sum + val, 0);
  }),
  
  // Memoized method with options
  expensiveTransform: d(function(transformType) {
    console.log(`Performing ${transformType} transform...`);
    return this.data.map(val => {
      switch(transformType) {
        case 'double': return val * 2;
        case 'square': return val * val;
        default: return val;
      }
    });
  }, { maxAge: 60000 }),  // Cache for 1 minute
  
  // Async memoized method
  fetchRelatedData: d(function(endpoint, callback) {
    console.log(`Fetching from ${endpoint}...`);
    setTimeout(() => {
      callback(null, { endpoint, data: `data-for-${this.data[0]}` });
    }, 100);
  }, { async: true })
  
}));

// Usage
const processor1 = new DataProcessor([1, 2, 3, 4, 5]);
const processor2 = new DataProcessor([10, 20, 30]);

processor1.calculateSum();  // "Computing sum...", returns 15
processor1.calculateSum();  // Cache hit, returns 15 (no console output)

processor2.calculateSum();  // "Computing sum...", returns 60 (different instance)
processor2.calculateSum();  // Cache hit for processor2

// Each instance has its own cache
processor1.expensiveTransform('double');  // Computed for processor1
processor2.expensiveTransform('double');  // Computed for processor2 (different cache)

Advanced Method Configuration

Configure memoized methods with complex options including custom normalizers and argument handling.

/**
 * Advanced method memoization options
 */
const descriptors = memoizeMethods({
  methodName: d(function(...args) {
    // Method implementation
  }, {
    // Standard memoization options
    length: number,
    primitive: boolean,
    maxAge: number,
    max: number,
    async: boolean,
    promise: boolean,
    
    // Method-specific options
    getNormalizer: function(length) {
      // Return custom normalizer function
      return function(args) {
        return customKeyGeneration(args);
      };
    }
  })
});

Usage Examples:

const memoizeMethods = require("memoizee/methods");
const d = require("d");

class ApiClient {
  constructor(baseUrl, apiKey) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }
}

Object.defineProperties(ApiClient.prototype, memoizeMethods({
  
  // Custom normalizer for complex object arguments
  fetchUserData: d(function(userQuery) {
    console.log("Fetching user data...");
    return fetch(`${this.baseUrl}/users`, {
      method: 'POST',
      body: JSON.stringify(userQuery),
      headers: { 'Authorization': `Bearer ${this.apiKey}` }
    }).then(r => r.json());
  }, {
    promise: true,
    maxAge: 300000,  // 5 minute cache
    getNormalizer: function(length) {
      return function(args) {
        // Normalize based on query content, not object identity
        return JSON.stringify(args[0]);
      };
    }
  }),
  
  // Method with argument resolvers
  processNumbers: d(function(num1, num2, operation) {
    console.log(`Processing: ${num1} ${operation} ${num2}`);
    switch(operation) {
      case 'add': return num1 + num2;
      case 'multiply': return num1 * num2;
      default: return 0;
    }
  }, {
    length: 3,
    resolvers: [Number, Number, String],  // Type coercion
    maxAge: 30000
  }),
  
  // Async method with size limiting
  heavyComputation: d(function(input, callback) {
    console.log("Starting heavy computation...");
    setTimeout(() => {
      const result = input.map(x => x ** 2).reduce((a, b) => a + b, 0);
      callback(null, result);
    }, 1000);
  }, {
    async: true,
    max: 50,  // Limit cache size per instance
    dispose: (result) => {
      console.log("Disposing computation result:", result);
    }
  })
  
}));

// Usage
const client = new ApiClient("https://api.example.com", "key123");

client.fetchUserData({ name: "Alice", active: true });
client.fetchUserData({ active: true, name: "Alice" }); // Cache hit (normalized)

client.processNumbers("10", "5", "add");    // 15 (with type coercion)
client.processNumbers(10, 5, "add");        // Cache hit

Instance-Specific Caching

Each object instance maintains its own separate cache for memoized methods.

/**
 * Instance isolation - each object gets its own method cache
 * Methods are memoized per instance, not globally
 */

Usage Examples:

const memoizeMethods = require("memoizee/methods");
const d = require("d");

class Calculator {
  constructor(name) {
    this.name = name;
    this.calculations = 0;
  }
}

Object.defineProperties(Calculator.prototype, memoizeMethods({
  fibonacci: d(function(n) {
    this.calculations++;
    console.log(`${this.name}: Computing fib(${n})`);
    if (n < 2) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  })
}));

const calc1 = new Calculator("Calculator A");
const calc2 = new Calculator("Calculator B");

// Each instance has separate cache
calc1.fibonacci(10);  // Computes and caches for calc1
calc2.fibonacci(10);  // Computes and caches for calc2 (separate cache)

calc1.fibonacci(10);  // Cache hit for calc1
calc2.fibonacci(10);  // Cache hit for calc2

console.log(`${calc1.name} calculations: ${calc1.calculations}`);
console.log(`${calc2.name} calculations: ${calc2.calculations}`);

// Cache management per instance
calc1.fibonacci.clear();       // Clears only calc1's cache
calc2.fibonacci.delete(10);    // Deletes only from calc2's cache

Method Cache Management

Per-Instance Cache Control

Access cache management methods on individual instances.

/**
 * Cache management methods available on each instance
 */
instance.methodName.delete(...args);    // Delete specific cache entry
instance.methodName.clear();            // Clear all cached results for this instance
instance.methodName._get(...args);      // Get cached value without execution
instance.methodName._has(...args);      // Check if result is cached

Usage Examples:

class DataAnalyzer {
  constructor(dataset) {
    this.dataset = dataset;
  }
}

Object.defineProperties(DataAnalyzer.prototype, memoizeMethods({
  analyzeData: d(function(analysisType) {
    console.log(`Analyzing ${analysisType}...`);
    return { type: analysisType, result: Math.random() };
  }, { maxAge: 60000 })
}));

const analyzer = new DataAnalyzer([1, 2, 3]);

analyzer.analyzeData('mean');      // Computed
analyzer.analyzeData('median');    // Computed

// Check cache status
console.log(analyzer.analyzeData._has('mean'));    // true
console.log(analyzer.analyzeData._has('mode'));    // false

// Get cached value
const cachedMean = analyzer.analyzeData._get('mean');
console.log(cachedMean);

// Clear specific entry
analyzer.analyzeData.delete('mean');

// Clear all cached results for this instance
analyzer.analyzeData.clear();

Reference Counting for Methods

Use reference counting with method memoization for sophisticated memory management.

/**
 * Reference counting methods for memoized methods
 */
const descriptors = memoizeMethods({
  methodName: d(function(...args) {
    // Method implementation
  }, { 
    refCounter: true 
  })
});

// Additional methods available with refCounter
instance.methodName.deleteRef(...args);      // Decrement reference
instance.methodName.getRefCount(...args);    // Get reference count

Usage Examples:

class ResourceManager {
  constructor(id) {
    this.id = id;
  }
}

Object.defineProperties(ResourceManager.prototype, memoizeMethods({
  createResource: d(function(resourceType) {
    console.log(`Creating ${resourceType} resource...`);
    return { 
      type: resourceType, 
      id: Math.random(),
      cleanup: () => console.log(`Cleaning up ${resourceType}`)
    };
  }, { 
    refCounter: true,
    dispose: (result) => {
      if (result && result.cleanup) result.cleanup();
    }
  })
}));

const manager = new ResourceManager("mgr-1");

const resource1 = manager.createResource('database');  // refs: 1
const resource2 = manager.createResource('database');  // refs: 2 (cache hit)

console.log(manager.createResource.getRefCount('database')); // 2

manager.createResource.deleteRef('database');  // refs: 1
manager.createResource.deleteRef('database');  // refs: 0, cleanup called

Advanced Method Patterns

Lazy Initialization with Memoization

Combine lazy initialization with method memoization for optimal performance.

class ExpensiveService {
  constructor(config) {
    this.config = config;
    // Don't initialize expensive resources in constructor
  }
}

Object.defineProperties(ExpensiveService.prototype, memoizeMethods({
  
  // Lazy initialization - only run once per instance
  initialize: d(function() {
    console.log("Initializing expensive service...");
    this.connection = createDatabaseConnection(this.config);
    this.cache = new Map();
    return this;
  }),
  
  // Use initialized resources
  queryData: d(function(query) {
    this.initialize(); // Ensure initialization (cached after first call)
    console.log("Querying data...");
    return this.connection.query(query);
  }, { 
    async: true,
    maxAge: 60000 
  })
  
}));

const service = new ExpensiveService({ host: 'localhost' });
// No expensive initialization yet

service.queryData('SELECT * FROM users');  // Initializes and queries
service.queryData('SELECT * FROM posts');  // Uses existing initialization

Method Inheritance and Memoization

Handle method memoization in inheritance hierarchies.

class BaseProcessor {
  constructor(data) {
    this.data = data;
  }
}

// Base class memoized methods
Object.defineProperties(BaseProcessor.prototype, memoizeMethods({
  baseProcess: d(function() {
    console.log("Base processing...");
    return this.data.length;
  })
}));

class AdvancedProcessor extends BaseProcessor {
  constructor(data, options) {
    super(data);
    this.options = options;
  }
}

// Extended class additional memoized methods
Object.defineProperties(AdvancedProcessor.prototype, memoizeMethods({
  advancedProcess: d(function(mode) {
    console.log("Advanced processing...");
    const baseResult = this.baseProcess(); // Uses memoized base method
    return baseResult * (this.options.multiplier || 1);
  }, { maxAge: 30000 })
}));

const advanced = new AdvancedProcessor([1, 2, 3], { multiplier: 2 });
advanced.baseProcess();     // Base method cached
advanced.advancedProcess('fast'); // Advanced method cached, reuses base result

Performance Considerations

Memory Usage Per Instance

Method memoization creates separate caches per instance:

// Consider memory usage with many instances
const instances = [];
for (let i = 0; i < 1000; i++) {
  const instance = new MyClass(data[i]);
  instance.expensiveMethod(params); // Each instance gets its own cache
  instances.push(instance);
}

// Use size limits for instances with large caches
Object.defineProperties(MyClass.prototype, memoizeMethods({
  expensiveMethod: d(function(params) {
    return heavyComputation(params);
  }, { 
    max: 100,  // Limit cache size per instance
    maxAge: 300000  // Auto-expire entries
  })
}));

Choosing Method Memoization

Method memoization is ideal when:

  • Instance-specific results: Results depend on instance state (this context)
  • Prototype optimization: Want to add caching to existing class hierarchies
  • Per-instance cache management: Need separate cache control per object
  • Object-oriented design: Working with class-based architectures

Use regular memoization when:

  • Stateless functions: Results don't depend on this context
  • Global caching: Want shared cache across all calls
  • Functional programming: Working with standalone functions

docs

async-functions.md

cache-management.md

function-memoization.md

index.md

method-memoization.md

profiling.md

weakmap-memoization.md

tile.json