npm package: @hyperfrontend/immutable-api-utils
Description: Decorators and utilities for creating immutable, tamper-proof object APIs.
Author: andrew.redican.mejia@gmail.com
Latest version: 0.1.2

# @hyperfrontend/immutable-api-utils

<p align="center">
  <a href="https://github.com/AndrewRedican/hyperfrontend/actions/workflows/ci-lib-immutable-api-utils.yml">
    <img src="https://img.shields.io/github/actions/workflow/status/AndrewRedican/hyperfrontend/ci-lib-immutable-api-utils.yml?style=flat-square&logo=github&label=build" alt="Build">
  </a>
  <a href="https://codecov.io/gh/AndrewRedican/hyperfrontend/flags?flags%5B0%5D=immutable-api-utils">
    <img src="https://codecov.io/gh/AndrewRedican/hyperfrontend/graph/badge.svg?flag=immutable-api-utils" alt="Coverage">
  </a>
  <a href="https://www.npmjs.com/package/@hyperfrontend/immutable-api-utils">
    <img src="https://img.shields.io/npm/v/@hyperfrontend/immutable-api-utils?style=flat-square" alt="npm version">
  </a>
  <a href="https://bundlephobia.com/package/@hyperfrontend/immutable-api-utils">
    <img src="https://img.shields.io/bundlephobia/min/%40hyperfrontend%2Fimmutable-api-utils?style=flat-square" alt="npm bundle size">
  </a>
</p>
<p align="center">
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
  <a href="#contributors">
    <img src="https://img.shields.io/github/all-contributors/AndrewRedican/hyperfrontend?color=ee8449&style=flat-square" alt="All Contributors">
  </a>
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
  <a href="https://github.com/AndrewRedican/hyperfrontend/blob/main/LICENSE.md">
    <img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License">
  </a>
  <a href="https://www.npmjs.com/package/@hyperfrontend/immutable-api-utils">
    <img src="https://img.shields.io/npm/dm/@hyperfrontend/immutable-api-utils?style=flat-square" alt="npm downloads">
  </a>
  <a href="https://github.com/AndrewRedican/hyperfrontend">
    <img src="https://img.shields.io/github/stars/AndrewRedican/hyperfrontend?style=flat-square" alt="GitHub stars">
  </a>
  <img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square&logo=node.js" alt="Node Version">
  <img src="https://img.shields.io/badge/tree%20shakeable-%E2%9C%93-success?style=flat-square" alt="Tree Shakeable">
</p>

Decorators and utilities for creating immutable, tamper-proof object APIs with built-in prototype pollution defense.

• 👉 See [**documentation**](https://www.hyperfrontend.dev/docs/libraries/utils/immutable-api/)

## What is @hyperfrontend/immutable-api-utils?

@hyperfrontend/immutable-api-utils provides low-level utilities for locking object properties and methods to prevent modification. Using JavaScript's property descriptors (`Object.defineProperty`), it creates truly immutable APIs where neither values nor method bindings can be altered after definition.

The library offers three approaches: a TypeScript decorator (`@locked()`) for class methods, a functional API (`lockedProps()`) for bulk property locking, and descriptor builders (`lockedPropertyDescriptors()`) for granular control. All utilities enforce non-writable, non-configurable descriptors while maintaining correct `this` binding through per-instance caching.

Additionally, the library provides **safe built-in copies**—pre-captured references to JavaScript built-in methods that mitigate prototype pollution attacks when loaded early.

### Key Features

- **`@locked()` decorator** for TypeScript classes—prevents method overwriting and ensures correct `this` binding
- **Bulk property locking** via `lockedProps()` for multiple properties in one call
- **Property descriptor creation** with `lockedPropertyDescriptors()` for custom locking patterns
- **Safe built-in copies** via secondary entrypoints—captured at module load time before any pollution can occur
- **Per-instance binding cache** to avoid repeated `.bind()` calls
- **Zero runtime dependencies** - pure JavaScript property descriptor manipulation

### Architecture Highlights

The `@locked()` decorator uses Symbol-based caching to store bound methods per instance, avoiding the performance cost of repeated `.bind()` calls. Properties are marked `configurable: false` to prevent deletion or descriptor modification, and `writable: false` to block reassignment.

The safe built-in copies are captured at module initialization time. **Important:** This only works if the module loads before any malicious code runs—it mitigates pollution, not prevents it retroactively.

## Why Use @hyperfrontend/immutable-api-utils?

### Prevents Accidental API Tampering in Shared Contexts

When exposing objects to third-party code, plugin systems, or sandboxed environments, you need guarantees that critical methods won't be overwritten. `@locked()` makes methods truly immutable—attempting reassignment throws a TypeError. This protects public APIs from accidental or malicious modification.

### Eliminates `this` Binding Bugs Without Arrow Functions

Arrow functions in class fields break inheritance and bloat bundle sizes due to per-instance function creation. The `@locked()` decorator provides correct `this` binding (like arrow functions) while using efficient prototype methods. Methods are bound once per instance and cached with Symbol keys.

### Simplifies Immutable Object Construction

Building frozen objects with `Object.freeze()` is shallow and doesn't prevent descriptor modification. `lockedProps()` provides deep immutability for specific properties while allowing controlled mutability elsewhere—ideal for partially frozen configs or API surfaces.

### TypeScript-First with Runtime Enforcement

Unlike TypeScript `readonly` (compile-time only), these utilities enforce immutability at runtime. This catches bugs in JavaScript-land, during deserialization, or when interfacing with dynamically typed code. Type safety and runtime safety in one decorator.

## Installation

```bash
npm install @hyperfrontend/immutable-api-utils
```

## Quick Start

```typescript
import { locked, lockedProps, lockedPropertyDescriptors } from '@hyperfrontend/immutable-api-utils'

// Decorator usage: lock methods in classes
class Counter {
  private count = 0

  @locked()
  increment() {
    this.count++
    return this.count
  }

  @locked()
  getValue() {
    return this.count
  }
}

const counter = new Counter()
counter.increment() // Works: 1
counter.increment = () => 0 // Throws: Cannot overwrite locked method

// Ensure correct `this` binding even when method is extracted
const { increment } = counter
increment() // Still works correctly, `this` remains bound

// Functional API: lock multiple properties
const config = {}
lockedProps(config, [
  ['apiKey', 'secret-key-12345'],
  ['timeout', 5000],
  ['retries', 3],
])

config.apiKey = 'hacked' // Silent fail in non-strict mode, throws in strict mode
Object.defineProperty(config, 'apiKey', { writable: true }) // Throws: cannot redefine

// Low-level descriptor creation
const obj = {}
Object.defineProperty(obj, 'version', lockedPropertyDescriptors('1.0.0', true))
// Property is non-writable, non-configurable, but enumerable
```

## API Overview

### Decorator

- **`@locked()`** - TypeScript decorator that makes class methods immutable with correct `this` binding

### Functions

- **`lockedProps(object, pairs)`** - Lock multiple properties on an object with key-value pairs
- **`lockedPropertyDescriptors(value, enumerable?)`** - Create a locked property descriptor for manual use with `Object.defineProperty`

### Safe Built-in Copies

Pre-captured references to JavaScript built-ins via secondary entrypoints. Available modules:

| Entrypoint                   | Description                                                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `built-in-copy/object`       | Object static methods (`freeze`, `keys`, `entries`, etc.)                                                    |
| `built-in-copy/array`        | Array static methods (`isArray`, `from`, `of`)                                                               |
| `built-in-copy/json`         | JSON methods (`parse`, `stringify`)                                                                          |
| `built-in-copy/promise`      | Promise static methods and factory (`createPromise`, `all`, `race`, etc.)                                    |
| `built-in-copy/console`      | Console methods (`log`, `warn`, `error`, `info`, `debug`, etc.)                                              |
| `built-in-copy/timers`       | Timer functions (`setTimeout`, `setInterval`, `queueMicrotask`, `requestAnimationFrame`, etc.)               |
| `built-in-copy/messaging`    | Messaging APIs (`structuredClone`, `createMessageChannel`, `createBroadcastChannel`, `postMessage*` helpers) |
| `built-in-copy/encoding`     | Encoding APIs (`createTextEncoder`, `createTextDecoder`, `atob`, `btoa`)                                     |
| `built-in-copy/typed-arrays` | Typed arrays and buffers (`createUint8Array`, `createArrayBuffer`, `createDataView`, etc.)                   |
| `built-in-copy/url`          | URL APIs (`createURL`, `createURLSearchParams`, `canParse`, `createObjectURL`, etc.)                         |
| `built-in-copy/websocket`    | WebSocket factory (`createWebSocket`, ready state constants)                                                 |
| `built-in-copy/math`         | Math methods and constants (`random`, `floor`, `ceil`, `PI`, etc.)                                           |
| `built-in-copy/number`       | Number methods and constants (`isNaN`, `parseInt`, `parseFloat`, `MAX_SAFE_INTEGER`, etc.)                   |
| `built-in-copy/string`       | String static methods (`fromCharCode`, `fromCodePoint`, `raw`)                                               |
| `built-in-copy/reflect`      | Reflect methods                                                                                              |
| `built-in-copy/function`     | Function utilities                                                                                           |
| `built-in-copy/symbol`       | Symbol static methods                                                                                        |
| `built-in-copy/map`          | Map constructor factory                                                                                      |
| `built-in-copy/set`          | Set constructor factory                                                                                      |
| `built-in-copy/weak-map`     | WeakMap constructor factory                                                                                  |
| `built-in-copy/weak-set`     | WeakSet constructor factory                                                                                  |
| `built-in-copy/regexp`       | RegExp constructor factory                                                                                   |
| `built-in-copy/date`         | Date constructor factory                                                                                     |
| `built-in-copy/error`        | Error constructor factories                                                                                  |

**Limitations:**

- Only effective if imported before any untrusted code executes
- Does not protect against pollution that occurred before module load
- Best used as an early import in application entry points

```typescript
// Import early in your entry point
import { freeze, keys } from '@hyperfrontend/immutable-api-utils/built-in-copy/object'
import { parse } from '@hyperfrontend/immutable-api-utils/built-in-copy/json'
import { log, warn } from '@hyperfrontend/immutable-api-utils/built-in-copy/console'
import { setTimeout } from '@hyperfrontend/immutable-api-utils/built-in-copy/timers'
import { structuredClone, createMessageChannel } from '@hyperfrontend/immutable-api-utils/built-in-copy/messaging'

const config = freeze({ api: 'https://example.com' })
const data = parse('{"key": "value"}')
log('Config loaded:', config)
setTimeout(() => log('Delayed message'), 1000)
```

## Use Cases

- **Plugin APIs**: Prevent plugins from modifying core library methods
- **Sandboxed execution**: Expose safe APIs to untrusted code
- **Configuration objects**: Lock critical config values after initialization
- **Public library interfaces**: Protect exported classes from mutation
- **Event emitters**: Prevent handler list manipulation
- **Prototype pollution mitigation**: Safe built-in copies reduce attack surface when loaded early
- **Secure logging**: Use safe `console` copies to prevent tampered log output
- **Safe timers**: Prevent timer functions from being hijacked
- **Cross-origin messaging**: Secure `postMessage` wrappers with captured references

## Compatibility

| Platform                      | Support |
| ----------------------------- | :-----: |
| Browser                       |   ✅    |
| Node.js                       |   ✅    |
| Web Workers                   |   ✅    |
| Deno, Bun, Cloudflare Workers |   ✅    |

### Output Formats

| Format | File                       | Tree-Shakeable |
| ------ | -------------------------- | :------------: |
| ESM    | `index.esm.js`             |       ✅       |
| CJS    | `index.cjs.js`             |       ❌       |
| IIFE   | `bundle/index.iife.min.js` |       ❌       |
| UMD    | `bundle/index.umd.min.js`  |       ❌       |

Secondary entrypoints (`built-in-copy/*`) are individually tree-shakeable—import only the built-ins you need.

### CDN Usage

```html
<!-- unpkg -->
<script src="https://unpkg.com/@hyperfrontend/immutable-api-utils"></script>

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@hyperfrontend/immutable-api-utils"></script>

<script>
  const { locked, lockedProps, lockedPropertyDescriptors } = HyperfrontendImmutableApiUtils
</script>
```

**Global variable:** `HyperfrontendImmutableApiUtils`

### Dependencies

None — zero external dependencies.

## Part of hyperfrontend

This library is part of the [hyperfrontend](https://github.com/AndrewRedican/hyperfrontend) monorepo.

**📖 [Full documentation](https://www.hyperfrontend.dev/docs/libraries/utils/immutable-api)**

## License

MIT