prop-scope
v0.1.0
Published
A lightweight TypeScript library for temporarily overwriting object properties during callback execution with automatic restoration
Maintainers
Readme
prop-scope
A lightweight TypeScript library for temporarily overwriting object properties during callback execution with automatic restoration.
🚀 Features
- Safe Property Overwriting: Temporarily modify object properties with automatic restoration
- Error-Safe: Properties are restored even if the callback throws an error
- Conditional Overwriting: Use the
IGNOREsymbol to conditionally skip property overwrites - Type-Safe: Full TypeScript support with proper type inference
- Zero Dependencies: Lightweight with no external dependencies
📦 Installation
npm install prop-scope🔧 Basic Usage
Simple Property Overwriting
import { withProps } from 'prop-scope';
const config = { debug: false, timeout: 5000 };
withProps(config, { debug: true, timeout: 10000 }, (originalValues) => {
console.log(config); // { debug: true, timeout: 10000 }
console.log(originalValues); // { debug: false, timeout: 5000 }
// Do something with the modified config
performDebugOperation();
});
console.log(config); // { debug: false, timeout: 5000 } - restored!Error-Safe Restoration
Properties are automatically restored even when errors occur:
import { withProps } from 'prop-scope';
const obj = { a: 1, b: 2 };
try {
withProps(obj, { a: 10, b: 20 }, (originalValues) => {
console.log(obj); // { a: 10, b: 20 }
throw new Error("Something went wrong!");
});
} catch (error) {
console.log(error.message); // "Something went wrong!"
console.log(obj); // { a: 1, b: 2 } - still restored!
}🎯 Advanced Usage
Conditional Overwriting with IGNORE
Use the IGNORE symbol to conditionally skip property overwrites:
import { withProps, IGNORE } from 'prop-scope';
const settings = { theme: 'dark', fontSize: 14, animations: true };
withProps(
settings,
{
theme: 'light',
fontSize: settings.fontSize > 16 ? 12 : IGNORE, // Only override if > 16
animations: false
},
(originalValues) => {
console.log(settings); // { theme: 'light', fontSize: 14, animations: false }
console.log(originalValues); // { theme: 'dark', animations: true }
// Note: fontSize is not in originalValues since it wasn't overwritten
}
);
console.log(settings); // { theme: 'dark', fontSize: 14, animations: true }Using REMEMBER for Value Protection and Restoration
Use the REMEMBER symbol to capture original values and ensure they're restored even if modified during callback execution:
import { withProps, REMEMBER } from 'prop-scope';
const user = { name: 'John', age: 30, role: 'admin' };
withProps(
user,
{
name: 'Jane', // Override name
age: REMEMBER, // Capture original age and ensure restoration
role: 'user' // Override role
},
(originalValues) => {
console.log(user); // { name: 'Jane', age: 30, role: 'user' }
console.log(originalValues); // { name: 'John', age: 30, role: 'admin' }
// Even if callback modifies the age during execution...
user.age = 999;
console.log(user); // { name: 'Jane', age: 999, role: 'user' }
// The REMEMBER ensures it will be restored to original value (30)
}
);
console.log(user); // { name: 'John', age: 30, role: 'admin' } - age restored to 30!Testing Configuration Scenarios
Perfect for testing different configurations:
import { withProps } from 'prop-scope';
const apiConfig = {
baseUrl: 'https://api.prod.com',
timeout: 5000,
retries: 3
};
// Test with different environments
const testConfigs = [
{ baseUrl: 'https://api.staging.com', timeout: 10000 },
{ baseUrl: 'https://api.dev.com', timeout: 2000, retries: 1 }
];
testConfigs.forEach((testConfig, index) => {
withProps(apiConfig, testConfig, () => {
console.log(`Testing config ${index + 1}:`, apiConfig);
// Run your tests here
runApiTests();
});
// apiConfig is automatically restored after each test
});Mocking Object Methods
Temporarily replace methods for testing:
import { withProps } from 'prop-scope';
const logger = {
log: (msg: string) => console.log(`[LOG] ${msg}`),
error: (msg: string) => console.error(`[ERROR] ${msg}`)
};
const mockLogs: string[] = [];
withProps(
logger,
{
log: (msg: string) => mockLogs.push(`LOG: ${msg}`),
error: (msg: string) => mockLogs.push(`ERROR: ${msg}`)
},
() => {
logger.log('Test message');
logger.error('Test error');
console.log(mockLogs); // ['LOG: Test message', 'ERROR: Test error']
}
);
// Original logger methods are restored
logger.log('Back to normal'); // Prints: [LOG] Back to normal📚 API Reference
withProps<T>(source, overwrites, callback)
Temporarily overwrites properties on a source object while executing a callback.
Parameters
source(T extends object): The object whose properties will be temporarily overwrittenoverwrites(Partial<{ [K in keyof T]: T[K] | typeof IGNORE | typeof REMEMBER }>): Object containing properties and values to overwritecallback((originalValues: Partial<T>) => U): Function to execute with the overwritten properties. Receives the original values as an argument
Returns
The return value of the callback function (U)
IGNORE
A sentinel symbol used to skip overwriting a property. Unlike null or undefined, which are treated as actual values, IGNORE tells withProps to leave that property unchanged.
const IGNORE: unique symbolREMEMBER
A sentinel symbol used to capture the original value of a property without overwriting it initially, but ensures the property is restored to its original value even if modified during callback execution. This is useful when you want to access the original value and guarantee restoration regardless of any modifications that occur within the callback.
const REMEMBER: unique symbol⚠️ Important Warnings
Concurrency and Asynchronous Code
Warning: Using withProps in concurrent or asynchronous contexts may cause race conditions since it mutates the source object temporarily.
// ❌ Avoid this - potential race condition
const obj = { value: 1 };
withProps(obj, { value: 2 }, async () => {
await someAsyncOperation(); // Other code might access obj during this time
});
// ✅ Better approach for async scenarios
const getModifiedObject = (original, overwrites) => ({ ...original, ...overwrites });
const modifiedObj = getModifiedObject(obj, { value: 2 });
await someAsyncOperation(modifiedObj);Null and Undefined Values
Property values null and undefined are treated as actual values to set:
const obj = { a: 'hello', b: 'world' };
withProps(obj, { a: null, b: undefined }, () => {
console.log(obj); // { a: null, b: undefined }
});
console.log(obj); // { a: 'hello', b: 'world' } - restored💡 Use Cases
- Testing: Mock object properties and methods during tests
- Configuration Management: Temporarily modify configuration objects
- Feature Flags: Conditionally enable/disable features during execution
- Development Tools: Create debugging utilities that modify behavior temporarily
- A/B Testing: Test different object states without permanent modification
- Value Protection: Use
REMEMBERto ensure properties are restored even if modified during callback execution
🔍 TypeScript Support
This library is written in TypeScript and provides full type safety:
interface Config {
debug: boolean;
apiUrl: string;
timeout: number;
}
const config: Config = {
debug: false,
apiUrl: 'https://api.example.com',
timeout: 5000
};
// ✅ Type-safe - all properties match Config interface
withProps(config, { debug: true, timeout: 10000 }, () => {
// config is properly typed
});
// ❌ TypeScript error - 'invalidProp' doesn't exist on Config
withProps(config, { invalidProp: true }, () => {});🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
📄 License
ISC License - see the LICENSE file for details.
