@hyperfrontend/immutable-api-utils
v0.1.2
Published
Decorators and utilities for creating immutable, tamper-proof object APIs.
Maintainers
Readme
@hyperfrontend/immutable-api-utils
Decorators and utilities for creating immutable, tamper-proof object APIs with built-in prototype pollution defense.
• 👉 See documentation
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 correctthisbinding- 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
npm install @hyperfrontend/immutable-api-utilsQuick Start
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 enumerableAPI Overview
Decorator
@locked()- TypeScript decorator that makes class methods immutable with correctthisbinding
Functions
lockedProps(object, pairs)- Lock multiple properties on an object with key-value pairslockedPropertyDescriptors(value, enumerable?)- Create a locked property descriptor for manual use withObject.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
// 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
consolecopies to prevent tampered log output - Safe timers: Prevent timer functions from being hijacked
- Cross-origin messaging: Secure
postMessagewrappers 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
<!-- 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 monorepo.
License
MIT
