A lightweight TypeScript package for wrapping object properties.
npm i @typedly/callback@1.0.0-beta.4 @typedly/controller@0.0.1-beta @typedly/descriptor@5.1.0-beta --save-peer
npm install @typescript-package/wrap-property --save-peer
import {
// Class.
WrapProperty, // Concrete class to wrap the property.
// Abstract.
WrapPropertyBase, // Foundational class for extension.
WrapPropertyCore, // The abstraction for base.
} from '@typescript-package/wrap-property';
WrapProperty
— Concrete class for wrapping properties on objects/classes.WrapPropertyBase
— Base abstraction to extend for custom wrappers.WrapPropertyCore
— The core abstraction.
new WrapProperty(target, key, descriptor?)
target
: The object or class whose property is to be wrapped.key
: The property key.descriptor
(optional): Options such asonGet
,onSet
,privateKey
,configurable
, andenumerable
.
This example demonstrates how to intercept reads and writes for a property.
import { WrapProperty } from '@typescript-package/wrap-property';
class MyClass {
myProp = 'hello';
}
const inst = new MyClass();
export const wrapped = new WrapProperty(inst, 'myProp', {
active: true as boolean,
enabled: true as boolean,
onGet(key, previousValue, value) {
console.log(
`Accessed ${String(key)}`,
previousValue,
'→',
value
);
return value;
}
});
console.debug(`controller:`, wrapped.controller); // undefined, as no controller is set.
console.debug(`descriptor:`, wrapped.descriptor); // {active: true, configurable: true, enumerable: false, previousDescriptor: {…}, privateKey: '_myProp', …}
inst.myProp; // Logs: Accessed myProp hello → hello
wrapped.descriptor.enabled = false; // Disable the descriptor.
console.debug(inst.myProp); // undefined
wrapped.descriptor.enabled = true; // Enable the descriptor.
wrapped.descriptor.active = false; // Disable the active to not execute the `onGet` and `onSet` callbacks.
console.debug(inst.myProp); // hello
You can pass a class constructor to wrap its prototype property:
import { WrapProperty } from '@typescript-package/wrap-property';
export class Example {
public static readonly staticValue = 42;
public value = 1;
}
// Wrap the "value" property to log whenever it is accessed or set.
export const wrapped1 = new WrapProperty(
Example.prototype,
'value',
{
onGet(key, previousValue, value) {
console.log(`Getting "${String(key)}":`, value);
return value;
},
onSet(value, previousValue, key) {
console.log(`Setting "${String(key)}":`, previousValue, '→', value);
return value;
},
}
);
const example = new Example();
delete (example as any).value; // Remove the own property to ensure the wrap works.
example.value = 42; // Logs: Setting "value": 1 → 42
example.value; // Logs: Getting "value": 42
You can extend WrapPropertyBase
to implement your own custom behavior:
import { WrapPropertyBase } from '@typescript-package/wrap-property';
class CustomLogger extends WrapPropertyBase<typeof obj, 'someKey'> {
constructor(object: typeof obj, key: 'someKey') {
super(object, key, {
onGet: (k, v) => {
console.log(`Custom get for ${String(k)}: ${v}`);
return v;
}
});
super.wrap(object, key); // wrap.
}
}
const obj = { someKey: 123 };
new CustomLogger(obj, 'someKey');
obj.someKey; // Logs: Custom get for someKey: 123
import { WrapProperty } from '@typescript-package/wrap-property';
export class TestObject {
firstName = 'First name';
lastName = 'Last name';
age = 1000;
constructor(param11: number, param22: string) {}
}
const object = new TestObject(0, 'test');
let wrappedAgeFirst = new WrapProperty(object, 'age', { privateKey: '_age' });
let wrappedAgeSecond = new WrapProperty(object, 'age', { privateKey: '__age' });
let wrappedAgeThird = new WrapProperty(object, 'age', { privateKey: '___age' });
wrappedAgeSecond.unwrap(); // Unwraps the last, the field __age is not connected.
object.age = 3000;
console.debug(object.age); // 3000
Your contributions are valued! If you'd like to contribute, please feel free to submit a pull request. Help is always appreciated.
If you find this package useful and would like to support its and general development, you can contribute through one of the following payment methods. Your support helps maintain the packages and continue adding new.
Support via:
or via Trust Wallet
By participating in this project, you agree to follow Code of Conduct.
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner, and
- PATCH version when you make backwards-compatible bug fixes.
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
FAQ How should I deal with revisions in the 0.y.z initial development phase?
The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release.
How do I know when to release 1.0.0?
If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backwards compatibility, you should probably already be 1.0.0.
MIT © typescript-package (license)