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.
- 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.
npm install dx-object-jsimport { 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 }Creates a new observable proxy.
| Parameter | Type | Description |
|---|---|---|
target |
Object |
(Optional) The initial object/array to observe. Defaults to {}. |
options |
Object |
(Optional) Configuration options. |
| 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. |
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.
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'
}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
}
});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}`);
}
});- 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.
MIT License.
Copyright © 2026 OKZGN