deep-freeze
Recursively Object.freeze() objects and functions, safely in strict mode.
Repository Health
Technical Analysis
deep-freeze-strict is a tiny, single-purpose utility that recursively applies Object.freeze() to every nested object and function reachable from a given root value. Where the built-in Object.freeze() only freezes the top-level object, this library walks own properties depth-first and freezes each one that is itself an object or function, giving you a fully immutable structure in one call.
The “strict” fork exists to fix a specific footgun in the original deep-freeze package: freezing a function and then accessing its caller, callee, or arguments properties throws a TypeError under strict mode ("use strict"). This package explicitly skips those three properties when the frozen value is a function, so it can be used safely in strict-mode codebases and test suites without crashing.
It has no dependencies, a single ~20-line implementation file, and is commonly reached for in test fixtures and Redux-style reducers where guaranteeing an object was never mutated is the whole point of the test.
What You Get
- A single
deepFreeze(obj)function that recursively freezes an object graph, including nested objects, arrays, and functions - Strict-mode safety — skips
caller,callee, andargumentson frozen functions so it never throws under"use strict" - Zero runtime dependencies and a tiny (~20 line) implementation, easy to audit or vendor
- Works on any enumerable own property via
Object.getOwnPropertyNames, including non-enumerable-but-own properties, not just plain data properties
Common Use Cases
- Asserting in tests that a reducer or pure function never mutates its input by deep-freezing fixtures before passing them in
- Freezing Redux/Flux store state in development to catch accidental mutations early
- Locking down shared configuration objects so they can’t be altered after initialization
- Freezing constructors or prototypes (e.g.
Buffer) to guarantee no code path can monkey-patch them at runtime
Under The Hood
Architecture
The entire library is a single exported function, deepFreeze, in index.js. It has no internal modules, no class hierarchy, and no configuration surface — it takes one value, freezes it, and recurses into each own property that is itself an object or function via Object.getOwnPropertyNames, skipping properties already frozen (checked with Object.isFrozen) to avoid redundant work on shared/repeated references. There is nothing that could be described as layered or event-driven; the whole contract is a synchronous, side-effecting call that returns its input. Changing the core recursion would mean touching the only function in the package.
Tech Stack
Plain CommonJS JavaScript (module.exports) with zero runtime dependencies, published for any Node.js environment (and usable in browsers via bundlers, given its size). The only devDependency is tap (~0.3.0), used purely for the test suite via tap test/*.js. There is no build step, no TypeScript, no bundler configuration — the published index.js is the source file itself.
Code Quality
Tests live in test/*.js and use tap’s test()/t.plan()/t.ok()/t.equal() API, covering three scenarios: freezing an object containing a function property, freezing a null-prototype object (Object.create(null)), and the strict-mode function-freezing case that motivated this fork. There are no type annotations (plain JS, no TypeScript or JSDoc types), no linter configuration, and no CI config beyond a .travis.yml file. Error handling is minimal by design — the function either freezes successfully or lets Object.freeze’s own exceptions propagate; there’s no defensive validation of the input argument.
API Design
The API surface is a single function taking one argument and returning it, which is about as low-friction as an integration can be — var deepFreeze = require('deep-freeze-strict'); deepFreeze(obj). The naming is unambiguous and matches the well-known Object.freeze mental model. Documentation is a short README with a runnable example (example/deep.js) demonstrating freezing Buffer and its prototype. There is no configuration, no options object, and nothing to learn beyond the single call — the tradeoff is that behavior (e.g. skipping caller/callee/arguments on functions) is implicit rather than documented as a formal contract, though it is explained in the README’s rationale for the strict-mode fork.