trapify Service
Interface
trapify(object): ProxyObject
Parameters
object
(Object) - The object to wrap with proxy traps. Can contain special trap methods that define custom behaviors.
Returns
- ProxyObject - A double-wrapped proxy that provides custom get/set behavior and supports all JavaScript proxy traps
Description
The trapify
service is a powerful utility that creates enhanced proxy objects with customizable behavior for property access, method calls, and other object operations. It provides a clean interface for implementing missing property handlers, custom getters/setters, and all standard JavaScript proxy traps.
The service creates a double proxy structure:
- Inner proxy: Handles basic get/set operations with support for
__getMissing
and__setMissing
hooks - Outer proxy: Dynamically registers handlers for all standard proxy traps based on methods prefixed with
__
Key features:
- Missing Property Handling: Automatically handles access to undefined properties
- Dynamic Trap Registration: Supports all 13 JavaScript proxy traps via
__
prefixed methods - Recursive Trapification: Maintains trapified context through method calls
- Custom Getter/Setter Logic: Respects existing property descriptors while adding custom behavior
Special Trap Methods
The trapify service recognizes several special methods that define custom behaviors:
__getMissing(target, propertyName, ...args)
Called when accessing a property that doesn't exist on the object.
target
- The original objectpropertyName
- Name of the missing property...args
- Additional arguments from the proxy get trap- Returns: Value to return for the missing property
__setMissing(target, propertyName, value, ...args)
Called when setting a property that doesn't have a setter.
target
- The original objectpropertyName
- Name of the property being setvalue
- Value being assigned...args
- Additional arguments from the proxy set trap- Returns: Boolean indicating if the set operation succeeded
Standard Proxy Trap Methods
All standard JavaScript proxy traps can be implemented by prefixing with __
:
__get
,__set
,__deleteProperty
,__ownKeys
,__has
__apply
,__construct
,__defineProperty
__getPrototypeOf
,__setPrototypeOf
__isExtensible
,__preventExtensions
__getOwnPropertyDescriptor
,__enumerate
Examples
Basic Missing Property Handler
const dynamicObject = trapify({
name: 'example',
__getMissing(target, prop) {
return `Missing property: ${prop}`;
}
});
console.log(dynamicObject.name); // 'example'
console.log(dynamicObject.anything); // 'Missing property: anything'
CSS Class Name Generator
// Used in cssClassesFor service
const classGenerator = trapify({
__getMissing: (target, name) => `view-${hash}-${inflector.dasherize(name)}`
});
// Usage:
const classes = classGenerator;
console.log(classes.modalDialog); // 'view-abc123def0-modal-dialog'
console.log(classes.submitButton); // 'view-abc123def0-submit-button'
Service Consumer Pattern
// Used in ServiceConsumer for dynamic service access
const serviceConsumer = trapify({
context: context,
__getMissing(target, serviceName) {
if(ServiceFactory.mixins[serviceName]) {
return ServiceFactory.create(serviceName, this.context).create();
}
}
});
// Usage:
const database = serviceConsumer.database; // Dynamically creates database service
const renderer = serviceConsumer.renderHtml; // Dynamically creates renderHtml service
Event Proxy with Method Delegation
// Used in ComponentEvent to proxy DOM events
const eventProxy = trapify({
event: domEvent,
__get(target, name) {
const value = target.event[name];
if(value instanceof Node) {
return ComponentEvent.Component.instanceFor(value);
}
if(typeof value === 'function') {
return (...args) => value.call(target.event, ...args);
}
return value;
}
});
// Usage:
eventProxy.preventDefault(); // Calls event.preventDefault()
eventProxy.target; // Returns wrapped component if it's a Node
eventProxy.type; // Returns event.type
Database Dynamic Table Access
// Used in Database class for dynamic table/model access
const database = trapify({
client: dbClient,
info: { users: 'table', posts: 'table', settings: 'singleton' },
__getMissing(target, name) {
const type = this.info[name];
if(type === 'table') return this.table(name);
if(type === 'singleton') return this.singleton(name);
if(type === 'union') return this.union(name);
}
});
// Usage:
const users = await database.users; // Returns table('users')
const settings = await database.settings; // Returns singleton('settings')
const posts = await database.posts; // Returns table('posts')
Custom Setter Logic
const validatedObject = trapify({
_values: {},
__setMissing(target, prop, value) {
// Custom validation logic
if(typeof value === 'string' && value.length > 100) {
throw new Error(`Value for ${prop} too long`);
}
target._values[prop] = value;
return true;
},
__getMissing(target, prop) {
return target._values[prop];
}
});
// Usage:
validatedObject.title = 'Short title'; // Works
validatedObject.description = 'Very long description...'; // May throw error
console.log(validatedObject.title); // 'Short title'
Advanced Proxy Trap Implementation
const advancedProxy = trapify({
data: { count: 0 },
__has(target, prop) {
console.log(`Checking if ${prop} exists`);
return prop in target.data;
},
__ownKeys(target) {
console.log('Getting all keys');
return Object.keys(target.data);
},
__deleteProperty(target, prop) {
console.log(`Deleting ${prop}`);
delete target.data[prop];
return true;
}
});
// Usage:
'count' in advancedProxy; // Logs: "Checking if count exists"
Object.keys(advancedProxy); // Logs: "Getting all keys"
delete advancedProxy.count; // Logs: "Deleting count"
Use Cases
1. Dynamic Service Resolution
Perfect for dependency injection systems where services are resolved on-demand:
const serviceContainer = trapify({
__getMissing(target, serviceName) {
return ServiceFactory.create(serviceName);
}
});
2. Missing Property Fallbacks
Providing default values or computed properties for missing attributes:
const configWithDefaults = trapify({
env: 'production',
__getMissing(target, key) {
return process.env[key.toUpperCase()] || null;
}
});
3. Method Interception and Delegation
Wrapping objects to intercept and modify method calls:
const loggingProxy = trapify({
target: originalObject,
__getMissing(target, method) {
if(typeof target.target[method] === 'function') {
return (...args) => {
console.log(`Calling ${method} with`, args);
return target.target[method](...args);
};
}
return target.target[method];
}
});
4. Dynamic Property Generation
Creating objects that generate properties based on access patterns:
const mathProxy = trapify({
__getMissing(target, operation) {
return (a, b) => {
switch(operation) {
case 'add': return a + b;
case 'multiply': return a * b;
case 'subtract': return a - b;
default: throw new Error(`Unknown operation: ${operation}`);
}
};
}
});
// Usage: mathProxy.add(2, 3) returns 5
Implementation Details
The trapify service creates a sophisticated proxy structure:
-
Property Access Priority:
- Existing property descriptors (getters/values) take precedence
__getMissing
is called only for truly missing properties- Getters are called with trapified
this
context
-
Property Setting Priority:
- Existing setters take precedence
__setMissing
handles properties without setters- Setters receive trapified
this
context
-
Trap Method Registration:
- All methods starting with
__
+ trap name are automatically registered - Trap methods receive trapified
this
context - Original target is passed as first argument
- All methods starting with
-
Context Preservation:
- All callback methods maintain proper
this
context - Recursive trapification ensures consistent behavior
- Original object structure remains intact
- All callback methods maintain proper
Performance Considerations
- Double Proxy Overhead: Creates two proxy layers, slight performance cost
- Trap Method Lookup: Uses property descriptor traversal for method detection
- Recursive Trapification: May create proxy chains in deeply nested operations
- Memory Usage: Maintains references to original objects and trap methods
The trapify service is essential for creating flexible, dynamic objects in the Pinstripe framework, enabling patterns like service containers, dynamic property generation, and sophisticated object proxying.