CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-moize

Blazing fast memoization library for JavaScript with comprehensive configuration options and React support

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

argument-transformation.mddocs/

Argument Transformation

Methods for transforming and manipulating function arguments before caching, enabling advanced cache key customization and conditional cache updates.

Capabilities

Argument Transformation

Transform arguments before they are used to generate cache keys, allowing for normalization and custom cache key generation.

/**
 * Transform arguments before caching
 * @param transformer Function to transform the argument array (cache key)
 * @returns Moizer with argument transformation
 */
transformArgs<Transformer extends TransformKey>(
  transformer: Transformer
): Moizer<{ transformArgs: Transformer }>;

type TransformKey = (key: Key) => Key;
type Key<Arg extends any = any> = Arg[];

Usage Examples:

import moize from "moize";

// Normalize string arguments
const normalizeStrings = (key: any[]) => {
  return key.map(arg => 
    typeof arg === 'string' ? arg.toLowerCase().trim() : arg
  );
};

const processText = (text: string, options: { uppercase: boolean }) => {
  return options.uppercase ? text.toUpperCase() : text.toLowerCase();
};

const normalizedMemoized = moize.transformArgs(normalizeStrings)(processText);

console.log(normalizedMemoized("  Hello  ", { uppercase: true })); // Computed
console.log(normalizedMemoized("hello", { uppercase: true })); // Cached (normalized match)

// Sort array arguments for order-independent caching
const sortArrayArgs = (key: any[]) => {
  return key.map(arg => {
    if (Array.isArray(arg)) {
      return [...arg].sort();
    }
    return arg;
  });
};

const sumArray = (numbers: number[]) => numbers.reduce((a, b) => a + b, 0);
const orderIndependentSum = moize.transformArgs(sortArrayArgs)(sumArray);

console.log(orderIndependentSum([3, 1, 2])); // 6 (computed)
console.log(orderIndependentSum([1, 2, 3])); // 6 (cached - same sorted values)

// Extract specific properties for cache key
const extractUserKey = (key: any[]) => {
  return key.map(arg => {
    if (arg && typeof arg === 'object' && 'id' in arg && 'role' in arg) {
      return { id: arg.id, role: arg.role }; // Only use id and role for caching
    }
    return arg;
  });
};

const processUser = (user: { 
  id: string; 
  name: string; 
  email: string; 
  role: string; 
  lastLogin?: Date; 
}) => {
  return `${user.role.toUpperCase()}: ${user.name}`;
};

const userMemoized = moize.transformArgs(extractUserKey)(processUser);

const user1 = { id: "1", name: "Alice", email: "alice@example.com", role: "admin", lastLogin: new Date() };
const user2 = { id: "1", name: "Alice", email: "alice@newdomain.com", role: "admin" }; // Different email, no lastLogin

console.log(userMemoized(user1)); // Computed
console.log(userMemoized(user2)); // Cached (same id and role)

Conditional Cache Updates

Control when cache entries should be updated based on the current cache key.

/**
 * Conditionally update cache based on key analysis
 * @param updateCacheForKey Function that determines when to update cache
 * @returns Moizer with conditional cache updating
 */
updateCacheForKey<UpdateWhen extends UpdateCacheForKey>(
  updateCacheForKey: UpdateWhen
): Moizer<{ updateCacheForKey: UpdateWhen }>;

type UpdateCacheForKey = (key: Key) => boolean;

Usage Examples:

import moize from "moize";

// Update cache only for specific conditions
const shouldUpdateCache = (key: any[]) => {
  const [data, options] = key;
  
  // Always update if force refresh is requested
  if (options && options.forceRefresh) {
    return true;
  }
  
  // Update if data is marked as stale
  if (data && data.lastModified) {
    const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
    return data.lastModified > fiveMinutesAgo;
  }
  
  return false; // Use cached version otherwise
};

const processData = (
  data: { content: any; lastModified?: number }, 
  options: { forceRefresh?: boolean } = {}
) => {
  console.log('Processing data...');
  return { processed: data.content, timestamp: Date.now() };
};

const conditionalMemoized = moize.updateCacheForKey(shouldUpdateCache)(processData);

const oldData = { content: "test", lastModified: Date.now() - 10 * 60 * 1000 }; // 10 min ago
const freshData = { content: "test", lastModified: Date.now() }; // Now

console.log(conditionalMemoized(oldData)); // Uses cache if available
console.log(conditionalMemoized(freshData)); // Always recomputes (fresh data)
console.log(conditionalMemoized(oldData, { forceRefresh: true })); // Always recomputes (forced)

// Version-based cache updating
const versionBasedUpdate = (key: any[]) => {
  const [resource] = key;
  
  if (!resource || typeof resource.version !== 'number') {
    return true; // Update if no version info
  }
  
  // Update if version is different from what might be cached
  return true; // In practice, you'd compare with cached version
};

const fetchResource = (resource: { id: string; version: number }) => {
  console.log(`Fetching resource ${resource.id} v${resource.version}`);
  return { data: `Resource ${resource.id}`, version: resource.version };
};

const versionMemoized = moize.updateCacheForKey(versionBasedUpdate)(fetchResource);

// Time-based cache invalidation
const timeBasedUpdate = (key: any[]) => {
  const [, options] = key;
  
  if (options && options.timestamp) {
    const thirtySecondsAgo = Date.now() - 30 * 1000;
    return options.timestamp > thirtySecondsAgo; // Update if timestamp is recent
  }
  
  return false;
};

const timestampMemoized = moize.updateCacheForKey(timeBasedUpdate)(processData);

Combining Transformation Methods

Argument transformation methods can be combined with other moize features for sophisticated caching strategies.

import moize from "moize";

// Normalize and extract key properties
const normalizeAndExtract = (key: any[]) => {
  return key.map(arg => {
    if (typeof arg === 'string') {
      return arg.toLowerCase().trim();
    }
    if (arg && typeof arg === 'object') {
      // Extract only relevant properties
      const { id, type, status } = arg;
      return { id, type, status };
    }
    return arg;
  });
};

// Update based on data freshness
const updateForFreshData = (key: any[]) => {
  const [item] = key;
  if (item && item.updatedAt) {
    const oneHourAgo = Date.now() - 60 * 60 * 1000;
    return item.updatedAt > oneHourAgo;
  }
  return true; // Update if no timestamp
};

const processItem = (item: {
  id: string;
  type: string;
  status: string;
  data: any;
  updatedAt?: number;
  metadata?: any;
}) => {
  return {
    processedId: item.id,
    processedType: item.type.toUpperCase(),
    processedStatus: item.status,
    processedAt: Date.now()
  };
};

const advancedMemoized = moize
  .transformArgs(normalizeAndExtract)    // Normalize cache keys
  .updateCacheForKey(updateForFreshData) // Update for fresh data
  .maxSize(50)                           // Limit cache size
  .maxAge(3600000)                       // 1 hour TTL
  .profile('item-processing')            // Performance monitoring
  (processItem);

// Chaining with other specialized methods
const apiProcessor = async (endpoint: string, params: Record<string, any>) => {
  const response = await fetch(endpoint, {
    method: 'POST',
    body: JSON.stringify(params),
    headers: { 'Content-Type': 'application/json' }
  });
  return response.json();
};

const normalizeApiArgs = (key: any[]) => {
  const [endpoint, params] = key;
  return [
    endpoint.toLowerCase(),
    params ? Object.keys(params).sort().reduce((sorted, k) => {
      sorted[k] = params[k];
      return sorted;
    }, {} as any) : {}
  ];
};

const smartApiMemoized = moize.promise
  .transformArgs(normalizeApiArgs)
  .serialize
  .maxAge(60000)
  (apiProcessor);

Performance Considerations

  • Transformation overhead: Complex transformations add computation cost on every function call
  • Cache key size: Transformed keys should be reasonably sized for memory efficiency
  • Conditional updates: updateCacheForKey functions should be fast to avoid performance penalties

docs

argument-transformation.md

cache-introspection.md

cache-management.md

core-memoization.md

equality-comparison.md

index.md

specialized-memoization.md

statistics-profiling.md

utility-methods.md

tile.json