CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-immutability-helper

Mutate a copy of data without changing the original source

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

extension-system.mddocs/

Extension System

Advanced features for creating isolated contexts and adding custom commands. The extension system allows you to add your own commands to extend immutability-helper's functionality.

Capabilities

Context Class

The Context class provides an isolated environment for update operations with custom commands and equality functions.

class Context {
  /**
   * Create a new Context instance with isolated command set
   */
  constructor();
  
  /**
   * Add a custom command to this context
   * @param directive - Command name (with $ prefix)
   * @param fn - Function that implements the command
   */
  extend<T>(directive: string, fn: (param: any, old: T) => T): void;
  
  /**
   * Update function for this context
   * @param object - Object to update
   * @param spec - Update specification
   * @returns Updated object
   */
  update<T, C extends CustomCommands<object> = never>(
    object: T, 
    $spec: Spec<T, C>
  ): T;
  
  /**
   * Get the equality function used for change detection
   */
  get isEquals(): (x: any, y: any) => boolean;
  
  /**
   * Set a custom equality function for change detection
   */
  set isEquals(value: (x: any, y: any) => boolean);
}

Usage Examples:

import { Context } from "immutability-helper";

// Create isolated context
const myContext = new Context();

// Add custom command
myContext.extend('$addTax', function(tax, original) {
  return original + (tax * original);
});

// Use custom context
const price = { amount: 100 };
const withTax = myContext.update(price, {
  amount: { $addTax: 0.2 }
});
// Result: { amount: 120 }

// Custom equality function
myContext.isEquals = (a, b) => {
  // Custom deep equality logic
  return JSON.stringify(a) === JSON.stringify(b);
};

Global Extend Function

Add custom commands to the default global context.

/**
 * Add a custom command to the global context
 * @param directive - Command name (with $ prefix)
 * @param fn - Function that implements the command
 */
function extend<T>(directive: string, fn: (param: any, old: T) => T): void;

Usage Examples:

import update, { extend } from "immutability-helper";

// Add global custom command
extend('$addTax', function(tax, original) {
  return original + (tax * original);
});

// Use globally
const result = update({ price: 100 }, {
  price: { $addTax: 0.15 }
});
// Result: { price: 115 }

// String manipulation command
extend('$capitalize', function(_, original) {
  return typeof original === 'string' 
    ? original.charAt(0).toUpperCase() + original.slice(1).toLowerCase()
    : original;
});

const text = { title: 'hello world' };
const capitalized = update(text, {
  title: { $capitalize: null }
});
// Result: { title: 'Hello world' }

Advanced Extension Examples

Mathematical Operations

import { extend } from "immutability-helper";

// Add mathematical operations
extend('$multiply', (factor, original) => original * factor);
extend('$power', (exponent, original) => Math.pow(original, exponent));
extend('$round', (decimals, original) => {
  const multiplier = Math.pow(10, decimals || 0);
  return Math.round(original * multiplier) / multiplier;
});

const data = { value: 3.14159 };
const processed = update(data, {
  value: { $round: 2 }
});
// Result: { value: 3.14 }

Array Manipulation Commands

import update, { extend } from "immutability-helper";

// Custom array operations
extend('$shuffle', (_, original) => {
  if (!Array.isArray(original)) return original;
  const shuffled = [...original];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
});

extend('$take', (count, original) => {
  return Array.isArray(original) ? original.slice(0, count) : original;
});

const numbers = { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] };
const taken = update(numbers, {
  list: { $take: 3 }
});
// Result: { list: [1, 2, 3] }

Conditional Operations

import { extend } from "immutability-helper";

// Conditional update command
extend('$if', (condition, original) => {
  const { test, then: thenSpec, else: elseSpec } = condition;
  const shouldUpdate = typeof test === 'function' ? test(original) : test;
  
  if (shouldUpdate && thenSpec) {
    return update(original, thenSpec);
  } else if (!shouldUpdate && elseSpec) {
    return update(original, elseSpec);
  }
  
  return original;
});

const user = { age: 17, status: 'minor' };
const updated = update(user, {
  $if: {
    test: (obj) => obj.age >= 18,
    then: { status: { $set: 'adult' } },
    else: { status: { $set: 'minor' } }
  }
});

Autovivification Commands

Autovivification is the automatic creation of new arrays and objects when needed. JavaScript doesn't have this feature natively, which makes deep nested updates challenging. These custom commands solve this problem:

import update, { extend } from "immutability-helper";

// Auto-create missing nested structures
extend('$auto', function(value, object) {
  return object ? update(object, value) : update({}, value);
});

extend('$autoArray', function(value, object) {
  return object ? update(object, value) : update([], value);
});

// Usage example - building deep structures from empty state
const state = {};
const result = update(state, {
  users: { $autoArray: {
    0: { $auto: {
      name: { $set: 'Alice' },
      posts: { $autoArray: { $push: ['Hello World'] } }
    }}
  }}
});
// Result: { users: [{ name: 'Alice', posts: ['Hello World'] }] }

// Alternative approach without custom commands (manual autovivification)
const state2 = {};
const desiredState = {
  foo: [{ bar: ['x', 'y', 'z'] }]
};

const manualResult = update(state2, {
  foo: foo =>
    update(foo || [], {
      0: fooZero =>
        update(fooZero || {}, {
          bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
        })
    })
});
// Result matches desiredState

Custom Command Function Signature

Custom command functions receive three parameters:

type CommandFunction<T> = (
  param: any,        // The parameter passed to the command
  nextObject: T,     // The current object being updated
  spec: any,         // The full specification object
  originalObject: T  // The original object before any updates
) => T;

Advanced Command Example:

extend('$increment', function(amount, nextObject, spec, originalObject) {
  // param: amount to increment
  // nextObject: current value during update chain
  // spec: the full spec object containing $increment
  // originalObject: the original value before any updates
  
  if (typeof nextObject === 'number') {
    return nextObject + (amount || 1);
  }
  return nextObject;
});

Type Safety with Custom Commands

For TypeScript users, you can create type-safe custom commands:

import update, { CustomCommands, Spec } from "immutability-helper";

// Define custom command types
interface MyCommands {
  $addTax: number;
  $capitalize: null;
}

// Create typed update function
function myUpdate<T>(object: T, spec: Spec<T, CustomCommands<MyCommands>>) {
  return update(object, spec);
}

// Usage with full type safety
const result = myUpdate({ price: 100, name: 'product' }, {
  price: { $addTax: 0.2 },    // TypeScript knows this expects a number
  name: { $capitalize: null }  // TypeScript knows this expects null
});

Error Handling

  • Custom command functions should handle their own validation
  • Use the invariant function for consistent error messages
  • Commands that don't modify data should return the original object reference
import { invariant } from "immutability-helper";

extend('$safeIncrement', function(amount, original) {
  invariant(
    typeof original === 'number',
    () => `$safeIncrement expects a number, got ${typeof original}`
  );
  
  invariant(
    typeof amount === 'number',
    () => `$safeIncrement amount must be a number, got ${typeof amount}`
  );
  
  return original + amount;
});

Performance Considerations

  • Custom commands should preserve reference equality when no changes occur
  • Avoid expensive operations in frequently called commands
  • Consider using the fourth parameter (originalObject) for optimization decisions
  • Custom equality functions affect performance - keep them fast and consistent

docs

array-operations.md

extension-system.md

function-operations.md

index.md

map-set-operations.md

object-operations.md

tile.json