dx-object-js
v4.1.0
Published
A deeply observable Proxy with safe auto-vivification. Accessing missing paths returns 'Ghost' proxies that materialize upon assignment. Features comprehensive mutation, deletion, and undefined-access tracking via configurable callbacks (onMutation, onUnd
Maintainers
Readme
DX Object
A lightweight, zero-dependency library that creates a deeply observable Proxy with safe auto-vivification. It allows you to access any nested path—even if it doesn't exist—by returning a "Smart Ghost Proxy." As soon as you assign a value to that path, the full object tree is materialized, and the mutation is reported. Features comprehensive mutation, deletion, and undefined-access tracking via configurable callbacks (onMutation, onUndefined, onError). ESM only.
💡 Key Features
- Safe Auto-vivification: Access nested properties without checking for existence (
form.user.address.street = '...'works even ifuseris undefined). - Deep Observation: Tracks mutations via a centralized
onMutationcallback. - Security First: Hardened against Prototype Pollution (
__proto__,constructor,prototype). - Depth Control: Configurable
maxDepthto prevent runaway paths from untrusted data. - Array Awareness: Deeply observes array mutators (
push,pop,splice, etc.) with structured payloads.
🚀 Installation
npm install dx-object-js⚡ Quick Start
import { DXObject } from 'dx-object-js';
const data = DXObject({}, {
onMutation: (path, value) => {
console.log(`Path: ${path.join('.')}, Value:`, value);
}
});
// Auto-vivification: Accessing non-existent paths creates a "Smart Ghost Proxy"
// Setting a value materializes the path.
data.user.profile.name = 'John Doe';
// Output: Path: user.profile.name, Value: John Doe
// Array mutations are tracked
data.cart.items.push({ id: 101 });
// Output: Path: cart.items, Value: { method: 'push', args: [{id: 101}], result: 1 }📖 API Reference
DXObject(target, options)
Creates a new observable proxy.
| Parameter | Type | Description |
| :--- | :--- | :--- |
| target | Object | (Optional) The initial object/array to observe. Defaults to {}. |
| options | Object | (Optional) Configuration options. |
DXObjectOptions
| Option | Type | Description |
| :--- | :--- | :--- |
| onMutation | Function | Callback for every successful mutation. |
| onUndefined | Function | Interceptor for non-existent properties. Return a value to override Smart Ghost Proxy creation. |
| onError | Function | Callback for dangerous operations (Prototype Pollution, depth overflow). Defaults to console.error. |
| arrayMutators | string[] | Additional array method names to intercept (e.g., ['fill', 'copyWithin']). |
| depth | number | Maximum smart-ghost-chain depth (1-100). Default: 10. |
Callbacks
onMutation(path, payload, parentProxy, root)
Invoked after every successful mutation.
path:string[]- The dot-separated key path (e.g.,['user', 'settings', 'theme']).payload: The new scalar value, or anArrayMutationPayload/PropertyDeletionPayload.parentProxy: The proxy wrapping the direct parent object.root: The original raw target object.
Mutation Payloads
Array Mutation Payload
If a method like push or splice is called:
{
method: 'push',
args: ['new-item'],
result: 1
}Property Deletion Payload
If delete is called:
{
property: 'fieldName',
value: 'originalValue'
}📖 Advanced Usage
Customizing "Not Found" Behavior
Use onUndefined to prevent the creation of a Smart Ghost Proxy for specific paths or return custom default values.
const config = DXObject({}, {
onUndefined: (path) => {
// Return null instead of a Proxy for properties containing 'legacy'
if (path.includes('legacy')) return null;
return undefined; // Let the library create the Smart Ghost Proxy
}
});Safety & Depth Limits
To prevent attacks using deeply nested objects (e.g., from untrusted JSON input), DXObject limits path creation depth.
const form = DXObject({}, {
depth: 5,
onError: (msg, ctx) => {
alert(`Security Warning: ${msg}`);
}
});Limitations & Best Practices
- Shared Object References: If the same object reference is assigned to multiple paths (e.g.,
form.a = obj; form.b = obj), the proxy cache associates the object with the first cache entry registered. Subsequent mutations on either path will report the first path. - Inherited Setters:
DXObjectguards the direct key. It cannot intercept logic inside custom setters defined on the prototype chain of an object. If that ocurrs, thenonMutationwill be executed like the mutation happens on the target object path. - Truthiness: Because accessing non-existent paths returns a "Smart Ghost Proxy",
if (form.user)will always evaluate to true.- Workaround: Check against specific values (
'user' in form) or check existence viaObject.keys()/hasOwnProperty.
- Workaround: Check against specific values (
- Native Objects: Passing complex native objects (Date, Map, Set) works, but internal slots are accessed via binding to the raw target. While standard for Proxy usage, avoid complex class instances with private state if possible.
📝 License
MIT License.
Copyright © 2026 OKZGN
