🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

constancy

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

constancy

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

Source
npmnpm
Version
2.0.0
Version published
Weekly downloads
27
22.73%
Maintainers
1
Weekly downloads
 
Created
Source

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

v1v2
Node.js >= 10Node.js >= 14 required
CommonJS-first (index.js)ESM-first ("type": "module")
Object.freeze polyfill includedRemoved — native in all supported environments
import constancy from 'constancy'Same — default import still works
No deepFreezedeepFreeze() 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

Keywords

constancy

FAQs

Package last updated on 08 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts