UNPKG

@typescript-package/wrap-property

Version:

A lightweight TypeScript package for wrapping object properties.

327 lines (321 loc) 12.8 kB
/** * @description The core abstraction class for wrapping properties. * @export * @abstract * @class WrapPropertyCore * @template {Record<PropertyKey, any>} O The type of the object. * @template {keyof O} [K=keyof O] The key type of the property to wrap. * @template {boolean} [A=boolean] The type of active property. * @template {boolean} [F=boolean] The type of enabled property. * @template {boolean} [C=boolean] The type of configurable property. * @template {boolean} [E=boolean] The type of enumerable property. * @template {WrappedPropertyDescriptor<O, K, A, C, E>} [D=WrappedPropertyDescriptor<O, K, A, C, E>] The type of descriptor constrained by the `WrappedPropertyDescriptor`. * @template {object | undefined} [R=undefined] The type of controller that controls the wrapping behavior. */ class WrapPropertyCore { /** * @description Defaults for active. * The active property indicates whether the callbacks `onGet` and `onSet` are active. * @public * @static * @type {boolean} */ static active = true; /** * @description Defaults for configurable. * @public * @static * @type {boolean} */ static configurable = true; /** * @description Defaults for enabled state of wrapped property. * If `true`, the property stores the value in the private key. * If `false`, the property does not store the value in the private key. * @public * @static * @type {boolean} */ static enabled = true; /** * @description Defaults for enumerable. * @public * @static * @type {boolean} */ static enumerable = false; /** * @description The controller that controls the wrapping behavior. * @public * @abstract * @readonly * @type {R} */ get controller() { return undefined; } } // Abstract. /** * @description The foundational class for wrapping properties. * @export * @abstract * @class WrapPropertyBase * @template {Record<PropertyKey, any>} O The type of the object. * @template {keyof O} [K=keyof O] The key type of the property to wrap. * @template {boolean} [A=boolean] The type of active property. * @template {boolean} [F=boolean] The type of enabled property. * @template {boolean} [C=boolean] The type of configurable property. * @template {boolean} [E=boolean] The type of enumerable property. * @template {WrappedPropertyDescriptor<O, K, C, E>} [D=WrappedPropertyDescriptor<O, K, C, E>] The type of descriptor constrained by the `WrappedPropertyDescriptor`. * @template {PropertyControllerShape<O, K, A, C, E, D>} [R=PropertyControllerShape<O, K, A, C, E, D>] The type of controller that controls the wrapping behavior. * @extends {WrapPropertyCore<T, O, K, C, E, D, R>} */ class WrapPropertyBase extends WrapPropertyCore { /** * @inheritdoc * @public * @readonly * @type {R} */ get controller() { return this.#controller; } /** * @description The descriptor that handles actual wrapped property. * @public * @readonly * @type {D} */ get descriptor() { return this.controller ? this.controller.descriptor : this.#descriptor; } /** * @description The key of the property to wrap. * @protected * @readonly * @type {K} */ get key() { return this.#key; } /** * @description The previous descriptor of the wrapped property. * @protected * @readonly * @type {*} */ get previousDescriptor() { return this.controller ? this.controller.descriptor.previousDescriptor : this.descriptor.previousDescriptor; } /** * @description The private key used to store the value of the property. * @protected * @readonly * @type {PropertyKey} */ get privateKey() { return (this.controller ? this.controller.descriptor.privateKey : this.descriptor.privateKey); } /** * @description The target object of the property. * @protected * @readonly * @type {T} */ get object() { return this.#object; } /** * @description Privately stored controller that controls the wrapping behavior. * @type {?R} */ #controller; /** * @description * @type {D} */ #descriptor; /** * @description Privately stored key of property. * @type {K} */ #key; /** * @description The target in which the property is wrapped. * @type {O} */ #object; /** * Creates an instance of `WrapPropertyBase` child class. * @constructor * @param {O} object The target object or class to wrap the property of the given `key`. * @param {K} key The key to wrap the property. * @param {?D} [descriptor] The descriptor of the property to wrap. * @param {?new (descriptor?: D) => R} [controller] The controller that controls the wrapping behavior. */ constructor(object, key, descriptor, controller) { super(); // If the descriptor is not provided, create a default one. descriptor = { ...{ active: WrapPropertyCore.active, configurable: WrapPropertyCore.configurable, enumerable: WrapPropertyCore.enumerable, previousDescriptor: Object.getOwnPropertyDescriptor(object, key), privateKey: `_${String(key)}` }, ...descriptor, }; // If the controller is provided, create an instance of it. controller ? this.#controller = new controller(descriptor) : this.#descriptor = descriptor; // The key to wrap. this.#key = key; // The target object to wrap the property on. this.#object = object; // Define the private property to store the value. this.#hasPrivateProperty(object) === false && this.#definePrivateProperty(object, key); } /** * @description Unwraps the property using previous descriptor. * @public * @returns {this} */ unwrap() { const previousDescriptor = (this.#controller ? this.#controller.descriptor : this.#descriptor).previousDescriptor; previousDescriptor && (Object.defineProperty(this.object, this.key, previousDescriptor), this.privateKey && delete this.object[this.privateKey]); return this; } /** * @description Wraps the property with a private indicator. * @protected * @param {O} object The object to wrap the property on. * @param {K} key The key of the property to wrap. * @returns {this} */ wrap(object, key) { // Wrap acts as controller. const controller = this.#controller ? this.#controller : this; // The getter for the property. const get = controller.descriptor.get ? controller.descriptor.get : function () { if (controller.descriptor.enabled === false) { return undefined; } // Set the this as the target object. const o = this; // Get the previous value from descriptor. const previousValue = controller.descriptor.previousDescriptor ? controller.descriptor.previousDescriptor.get && typeof controller.descriptor.previousDescriptor.get === 'function' ? controller.descriptor.previousDescriptor.get.call(this) : controller.descriptor.previousDescriptor.value : undefined; // Current descriptor. return controller.descriptor.onGet && ((typeof controller.descriptor.active === 'boolean' && controller.descriptor.active) || (typeof controller.descriptor.active === 'object' && controller.descriptor.active.onGet === true)) ? controller.descriptor.onGet.call(o, key, previousValue, o[controller.descriptor.privateKey], o) : o[controller.descriptor.privateKey]; }; // The setter for the property. const set = controller.descriptor.set ? controller.descriptor.set : function (value) { if (controller.descriptor.enabled === false) { return undefined; } // Set the this as the target object. const o = this; // Get the previous value from previous descriptor or current value. const previousValue = (o[controller.descriptor.privateKey] || controller.descriptor.previousDescriptor?.value); // Perform previous descriptor. controller.descriptor.previousDescriptor?.set && controller.descriptor.previousDescriptor.set.call(o, value); // Set the private property value. Object.assign(o, { [controller.descriptor.privateKey]: controller.descriptor.onSet && ((typeof controller.descriptor.active === 'boolean' && controller.descriptor.active) || (typeof controller.descriptor.active === 'object' && controller.descriptor.active.onSet === true)) ? controller.descriptor.onSet.call(o, value, previousValue, key, o) : value }); }; // Define property with the given key and options. Object.defineProperty(object, key, { configurable: controller.descriptor.configurable, // Whether the property can be deleted or changed. enumerable: controller.descriptor.enumerable, // Whether the property is visible in enumerations. get, // Getter for the property. set, // Setter for the property. }); return this; } /** * @description Checks if the object has a private property. * @param {O} object The object to check for the private property. * @returns {boolean} */ #hasPrivateProperty(object) { return Object.hasOwn(object, this.privateKey); } /** * @description Defines the private property in the object. * @param {O} object The object to define the private property on. * @param {K} key The key of the property to define. */ #definePrivateProperty(object, key) { Object.defineProperty(object, this.privateKey, { configurable: true, enumerable: false, value: object[key] || new object.constructor()[key], writable: true }); } } // Abstract. /** * @description The concrete class for wrapping object properties. * @export * @class WrapProperty * @template {object | (new () => any)} T The type of the target object or class to wrap the property of the given `key`. * @template {Record<PropertyKey, any>} [O=(T extends new () => T ? ( T extends { prototype: infer P } ? P : never) : T)] The object type of captured `T`. * @template {keyof O extends string | symbol ? keyof O : never} [K=keyof O extends string | symbol ? keyof O : never] The key type of the property to wrap. * @template {boolean} [A=boolean] The type of active property. * @template {boolean} [C=boolean] The type of configurable property. * @template {boolean} [E=boolean] The type of enumerable property. * @template {WrappedPropertyDescriptor<O, K, C, E>} [D=WrappedPropertyDescriptor<O, K, C, E>] The type of descriptor constrained by the `WrappedPropertyDescriptor`. * @template {PropertyControllerShape<O, K, A, C, E, D>} [Controller=PropertyControllerShape<O, K, A, C, E, D>] The type of controller that controls the wrapping behavior. * @extends {WrapPropertyBase<T, O, K, C, E, D>} */ class WrapProperty extends WrapPropertyBase { /** * Creates an instance of `WrapProperty`. * @constructor * @param {O} object The target object or class to wrap the property of the given `key`. * @param {K} key The key of the property to wrap. * @param {?D} [descriptor] The descriptor of the property to wrap. * @param {?new (descriptor?: D) => Controller} [controller] The controller that controls the wrapping behavior. */ constructor(object, key, descriptor, controller) { super(object, key, descriptor, controller); // Define the property with the given key and options. this.wrap(object, key); } } /* * Public API Surface of wrap-property */ /** * Generated bundle index. Do not edit. */ export { WrapProperty, WrapPropertyBase, WrapPropertyCore }; //# sourceMappingURL=typescript-package-wrap-property.mjs.map