Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.
—
Core functionality for creating immutable data structures and utility methods for type checking and configuration.
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 levelsAlias 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]);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)); // trueError 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."
}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 addedWhen to Use Static vs Instance API:
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 unchangedReact 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 immutablePromises 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 immutableThese 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)); // truePrimitives 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)); // trueThe library behaves differently depending on the build environment:
Development Build (process.env.NODE_ENV !== "production"):
Object.freeze()ImmutableError exceptionsProduction Build:
// 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]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