CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-seamless-immutable

Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.

Pending
Overview
Eval results
Files

core.mddocs/

Core Constructor and Utilities

Core functionality for creating immutable data structures and utility methods for type checking and configuration.

Capabilities

Immutable Constructor

Creates an immutable version of the provided data structure with backwards compatibility.

/**
 * Creates an immutable version of the provided data structure
 * @param {*} obj - Data to make immutable (Array, Object, Date, or primitive)
 * @param {Object} [options] - Configuration options
 * @param {Object} [options.prototype] - Custom prototype for objects
 * @param {number} [stackRemaining] - Stack depth limit for circular reference protection (default: 64, development only)
 * @returns {*} Immutable version of the input
 */
function Immutable(obj, options, stackRemaining);

Usage Examples:

const Immutable = require("seamless-immutable");

// Arrays become ImmutableArrays
const immutableArray = Immutable([1, 2, 3]);
console.log(Array.isArray(immutableArray)); // true - still an array!

// Objects become ImmutableObjects  
const immutableObj = Immutable({name: "Alice", age: 30});
console.log(typeof immutableObj); // "object"

// Dates become ImmutableDates
const immutableDate = Immutable(new Date());

// Primitives are returned as-is (already immutable)
const str = Immutable("hello"); // Returns "hello"
const num = Immutable(42); // Returns 42

// Custom prototype example
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return `Hello, I'm ${this.name}`;
};

const person = new Person("Bob");
const immutablePerson = Immutable(person, {prototype: Person.prototype});
console.log(immutablePerson.greet()); // "Hello, I'm Bob"

// Stack depth protection (development build only)
const deepObject = {};
let current = deepObject;
for (let i = 0; i < 100; i++) {
  current.nested = {};
  current = current.nested;
}
// Immutable(deepObject); // Would throw ImmutableError after default 64 levels

// Increase stack limit for legitimately deep (but not circular) objects
const allowDeeper = Immutable(deepObject, null, 256); // Allow 256 levels

Immutable.from

Alias for the main Immutable constructor, provided for linter compatibility.

/**
 * Alias for Immutable() for linter compatibility (e.g., ESLint new-cap rule)
 * @param {*} obj - Data to make immutable
 * @param {Object} [options] - Configuration options
 * @returns {*} Immutable version of the input
 */
Immutable.from = Immutable;

Usage Example:

// These are functionally identical
const arr1 = Immutable([1, 2, 3]);
const arr2 = Immutable.from([1, 2, 3]);

isImmutable

Checks whether a value is immutable according to seamless-immutable.

/**
 * Checks if a value is immutable
 * @param {*} target - Value to check for immutability  
 * @returns {boolean} True if the value is immutable
 */
function isImmutable(target);

Usage Examples:

const Immutable = require("seamless-immutable");

const mutableArray = [1, 2, 3];
const immutableArray = Immutable([1, 2, 3]);

console.log(Immutable.isImmutable(mutableArray)); // false
console.log(Immutable.isImmutable(immutableArray)); // true

// Primitives are considered immutable
console.log(Immutable.isImmutable("string")); // true
console.log(Immutable.isImmutable(42)); // true
console.log(Immutable.isImmutable(null)); // true

ImmutableError

Error class thrown when attempting to use mutating methods on immutable data structures.

/**
 * Error thrown when attempting to use mutating methods on immutable structures
 * @extends Error
 */
class ImmutableError extends Error {
  constructor(message: string);
}

Usage Example:

const Immutable = require("seamless-immutable");

const immutableArray = Immutable([1, 2, 3]);

try {
  immutableArray.push(4); // This will throw
} catch (error) {
  console.log(error instanceof Immutable.ImmutableError); // true
  console.log(error.message); // "The push method cannot be invoked on an Immutable data structure."
}

Static API

Alternative API that uses static methods instead of instance methods to avoid method name collisions.

/**
 * Static API version that avoids adding methods to object instances
 * All instance methods are available as static methods instead
 * When using static API, no instance methods are added to immutable objects
 */
const static;

Usage Examples:

const Immutable = require("seamless-immutable").static;

const obj = {name: "Alice", age: 30};
const immutableObj = Immutable(obj);

// Instead of: immutableObj.merge({age: 31})
const updated = Immutable.merge(immutableObj, {age: 31});

// Instead of: immutableObj.set("status", "active")  
const withStatus = Immutable.set(immutableObj, "status", "active");

// All static methods available:
// Immutable.merge, Immutable.set, Immutable.setIn, Immutable.without,
// Immutable.update, Immutable.updateIn, Immutable.getIn, Immutable.asMutable,
// Immutable.flatMap, Immutable.asObject

// Note: When using static API, instance methods are NOT available
console.log(typeof immutableObj.merge); // "undefined" - no instance methods added

When to Use Static vs Instance API:

  • Static API: Recommended to avoid method name pollution, better for functional programming style, no risk of conflicts with existing object methods
  • Instance API: More concise syntax, familiar object-oriented pattern, default behavior
  • Performance: Both APIs have identical performance characteristics

asMutable

Converts an immutable data structure back to a mutable one.

/**
 * Convert immutable structure to mutable copy
 * @param {*} obj - Immutable structure to convert
 * @param {Object} [options] - Conversion options
 * @param {boolean} [options.deep] - Recursively convert nested immutable structures
 * @returns {*} Mutable copy of the structure
 */
function asMutable(obj, options);

/**
 * Convert immutable Date to mutable copy (Date-specific)
 * @param {Date} date - Immutable Date to convert
 * @returns {Date} New mutable Date with same time value
 */
function asMutable(date);

Usage Examples:

const Immutable = require("seamless-immutable");

const immutableObj = Immutable({
  name: "Alice",
  hobbies: ["reading", "coding"],
  address: {city: "NYC", state: "NY"}
});

// Shallow conversion - nested structures remain immutable
const shallowMutable = Immutable.asMutable(immutableObj);
shallowMutable.name = "Bob"; // Works
// shallowMutable.hobbies.push("gaming"); // Would still throw error

// Deep conversion - all nested structures become mutable
const deepMutable = Immutable.asMutable(immutableObj, {deep: true});
deepMutable.name = "Charlie"; // Works
deepMutable.hobbies.push("gaming"); // Also works now
deepMutable.address.city = "LA"; // Also works now

console.log(Immutable.isImmutable(shallowMutable)); // false
console.log(Immutable.isImmutable(deepMutable)); // false

// Date-specific asMutable
const immutableDate = Immutable(new Date("2023-01-01"));
const mutableDate = immutableDate.asMutable();

// Date methods that would throw on immutable dates now work
mutableDate.setFullYear(2024);
console.log(mutableDate.getFullYear()); // 2024
console.log(immutableDate.getFullYear()); // 2023 - original unchanged

Special Behaviors

React Elements

React elements are treated as immutable and passed through unchanged to avoid interfering with React's internal structure.

const React = require("react");
const element = React.createElement("div", {className: "test"});
const result = Immutable(element);
console.log(element === result); // true - unchanged
console.log(Immutable.isImmutable(element)); // true - considered immutable

Promises

Promises themselves are not made immutable, but their fulfillment values are automatically made immutable using a transformed promise.

const promise = Promise.resolve([1, 2, 3]);
const wrappedPromise = Immutable(promise);

// The wrapped promise will resolve to an immutable array
wrappedPromise.then(result => {
  console.log(Immutable.isImmutable(result)); // true - array is now immutable
  console.log(Array.isArray(result)); // true - still an array
});

console.log(Immutable.isImmutable(wrappedPromise)); // false - promise itself not immutable

Functions, Errors, Files, Blobs

These types are treated as immutable and returned unchanged. This is an intentional abstraction leak for practical reasons.

const fn = function() { return "hello"; };
const error = new Error("test");
const file = new File(["content"], "test.txt");
const blob = new Blob(["content"]);

console.log(Immutable(fn) === fn); // true - unchanged
console.log(Immutable(error) === error); // true - unchanged  
console.log(Immutable(file) === file); // true - unchanged
console.log(Immutable(blob) === blob); // true - unchanged

// All are considered immutable
console.log(Immutable.isImmutable(fn)); // true
console.log(Immutable.isImmutable(error)); // true
console.log(Immutable.isImmutable(file)); // true  
console.log(Immutable.isImmutable(blob)); // true

Null and Undefined

Primitives including null and undefined are naturally immutable and returned unchanged.

console.log(Immutable(null) === null); // true
console.log(Immutable(undefined) === undefined); // true
console.log(Immutable.isImmutable(null)); // true
console.log(Immutable.isImmutable(undefined)); // true

Development vs Production Builds

The library behaves differently depending on the build environment:

Development Build (process.env.NODE_ENV !== "production"):

  • Objects and arrays are frozen using Object.freeze()
  • Mutating methods throw helpful ImmutableError exceptions
  • Circular reference protection with configurable stack depth (default 64 levels)
  • Date mutating methods are banned and throw errors

Production Build:

  • No freezing for better performance (~2x speed improvement)
  • No defensive method banning (methods still won't work, but fail silently)
  • No circular reference checking
  • Smaller bundle size
// Development build behavior
const arr = Immutable([1, 2, 3]);
try {
  arr.push(4); // Throws ImmutableError with helpful message
} catch (e) {
  console.log(e.message); // "The push method cannot be invoked on an Immutable data structure."
}

// Production build behavior  
const arr = Immutable([1, 2, 3]);
arr.push(4); // Fails silently, arr remains [1, 2, 3]

Circular Reference Protection

In development builds, the constructor includes protection against circular references with a configurable stack depth limit (default 64 levels).

// This would throw an ImmutableError after 64 levels of nesting (development only)
const deepObject = {};
let current = deepObject;
for (let i = 0; i < 100; i++) {
  current.nested = {};
  current = current.nested;
}
// Immutable(deepObject); // Throws error about deeply nested object

// Increase limit for legitimately deep objects
const deeperAllowed = Immutable(deepObject, null, 256);

Install with Tessl CLI

npx tessl i tessl/npm-seamless-immutable

docs

array-operations.md

core.md

index.md

nested-operations.md

object-operations.md

tile.json