npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

constancy

v2.0.0

Published

Lightweight utility to make objects and arrays immutable with shallow and deep freeze

Downloads

174

Readme

Constancy

npm version npm downloads TypeScript license test

Lightweight, zero-dependency TypeScript utility for making objects and arrays deeply immutable. Shallow and recursive freeze with full type safety.

Installation

npm install constancy
# or
yarn add constancy
# or
pnpm add constancy

Requires Node.js >= 14.

Quick Start

import constancy, { deepFreeze } from 'constancy';

// Shallow freeze — top-level properties only
const config = constancy({ host: 'localhost', port: 3000 });
config.port = 8080; // Ignored (TypeError in strict mode)

// Deep freeze — all nested properties recursively
const state = deepFreeze({
  user: { name: 'Alice', roles: ['admin'] },
  settings: { theme: 'dark' },
});
state.user.name = 'Bob';       // Ignored
state.user.roles.push('user'); // Ignored

API Reference

constancy<T>(val: T)

Shallow-freezes an object or function using Object.freeze(). Primitives are returned unchanged.

Returns: Readonly<T> for objects/functions, T for primitives.

import constancy from 'constancy';

constancy({ a: 1 });           // Readonly<{ a: number }>
constancy([1, 2, 3]);          // readonly number[]
constancy(() => 'hello');      // Frozen function (still callable)
constancy('text');             // 'text' — unchanged
constancy(42);                 // 42 — unchanged
constancy(null);               // null — unchanged

Nested objects are NOT frozen:

const obj = constancy({ user: { name: 'Alice' } });
obj.user.name = 'Bob'; // Works — nested is not frozen
obj.user = {};          // Ignored — top-level is frozen

deepFreeze<T>(val: T)

Recursively freezes a value and all reachable nested objects. Handles:

  • Circular references (via WeakSet — already-visited nodes are skipped)
  • Symbol-keyed properties (via Reflect.ownKeys())
  • TypedArrays (skipped — freezing non-empty TypedArrays throws natively)
  • Accessor descriptors (get/set) — skipped to avoid unintended invocation

Returns: DeepReadonly<T> for objects, T for primitives.

import { deepFreeze } from 'constancy';

const obj = deepFreeze({
  nested: { count: 0 },
  tags: ['a', 'b'],
});
obj.nested.count = 1; // Ignored
obj.tags.push('c');   // Ignored

// Circular references are safe
const node: any = { value: 1 };
node.self = node;
deepFreeze(node); // No infinite loop

Types

DeepReadonly<T>

Recursively marks all properties as readonly. Handles objects, arrays, Maps, and Sets.

import type { DeepReadonly } from 'constancy';

type Config = DeepReadonly<{
  db: { host: string; port: number };
  tags: string[];
}>;
// Config.db.host is readonly, Config.tags is ReadonlyArray<string>

Freezable

Union type of values that can be frozen: object | Function.

import type { Freezable } from 'constancy';

function freeze(val: Freezable) {
  return Object.freeze(val);
}

TypeScript Usage

Named and default imports are both supported:

// Default import
import constancy from 'constancy';

// Named imports
import { constancy, deepFreeze } from 'constancy';
import type { DeepReadonly, Freezable } from 'constancy';

CommonJS:

const { constancy, deepFreeze } = require('constancy');

Migration from v1

Breaking changes

| v1 | v2 | |---|---| | Node.js >= 10 | Node.js >= 14 required | | CommonJS-first (index.js) | ESM-first ("type": "module") | | Object.freeze polyfill included | Removed — native in all supported environments | | import constancy from 'constancy' | Same — default import still works | | No deepFreeze | deepFreeze() added | | No DeepReadonly<T> | Type exported |

Import changes

v1 root entry (index.js, index.d.ts) is removed. Imports via the package name continue to work through the exports field:

// Still works in v2
import constancy from 'constancy';
import { constancy, deepFreeze } from 'constancy';

Direct path imports (from 'constancy/dist/...') are not supported.

Development

# Build ESM + CJS bundles
npm run build

# Run tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

# Type check
npm run typecheck

Project structure

src/
├── index.ts        — barrel exports
├── constancy.ts    — shallow freeze
├── deep-freeze.ts  — recursive deep freeze
├── types.ts        — DeepReadonly<T>, Freezable
└── utils.ts        — isFreezable(), getOwnKeys()

tests/
├── constancy.test.ts     — 19 tests
└── deep-freeze.test.ts   — 11 tests

dist/               — built output (ESM + CJS + .d.ts)

Why Constancy?

  • Zero dependencies — no supply chain risk, no bloat
  • Tiny — under 1KB minified + gzipped
  • Type-safeReadonly<T> and DeepReadonly<T> inferred automatically
  • Safe — handles circular refs, Symbol keys, TypedArrays, getters
  • Dual format — ESM + CJS with conditional exports
  • Well-tested — 30 tests, ~97% coverage

License

MIT — DungGramer