UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

262 lines (222 loc) 6.54 kB
/** * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact schukai GmbH. * * SPDX-License-Identifier: AGPL-3.0 */ import { Base } from "./base.mjs"; import { isArray, isObject, isPrimitive } from "./is.mjs"; import { Observer } from "./observer.mjs"; import { ObserverList } from "./observerlist.mjs"; import { validateObject } from "./validate.mjs"; import { extend } from "../data/extend.mjs"; import { instanceSymbol, proxyInstanceMarker } from "../constants.mjs"; import { clone } from "../util/clone.mjs"; export { ProxyObserver }; /** * An observer manages a callback function * * With the ProxyObserver you can attach observer for observation. * With each change at the object to be observed, an update takes place. * * This also applies to nested objects. * * @license AGPLv3 * @since 1.0.0 * @copyright schukai GmbH */ class ProxyObserver extends Base { /** * * @param {object} object * @throws {TypeError} value is not a object */ constructor(object) { super(); this.realSubject = validateObject(object); this.subject = new Proxy(object, getHandler.call(this)); this.objectMap = new WeakMap(); this.objectMap.set(this.realSubject, this.subject); this.proxyMap = new WeakMap(); this.proxyMap.set(this.subject, this.realSubject); this.observers = new ObserverList(); } /** * This method is called by the `instanceof` operator. * @return {symbol} * @since 2.1.0 */ static get [instanceSymbol]() { return Symbol.for("@schukai/monster/types/proxy-observer"); } /** * @return {object} */ getSubject() { return this.subject; } /** * @since 1.24.0 * @param {Object} obj * @return {ProxyObserver} */ setSubject(obj) { let i; const clonedObject = clone(obj); const k = Object.keys(this.subject); for (i = 0; i < k.length; i++) { delete this.subject[k[i]]; } this.subject = extend(this.subject, clonedObject); return this; } /** * Retrieves the real subject associated with the current instance. * * @return {Object} The real subject object. */ getRealSubject() { return this.realSubject; } /** * attach a new observer * * @param {Observer} observer * @return {ProxyObserver} */ attachObserver(observer) { this.observers.attach(observer); return this; } /** * detach a observer * * @param {Observer} observer * @return {ProxyObserver} */ detachObserver(observer) { this.observers.detach(observer); return this; } /** * notify all observer * * @return {Promise} */ notifyObservers() { return this.observers.notify(this); } /** * @param {Observer} observer * @return {boolean} */ containsObserver(observer) { return this.observers.contains(observer); } } /** * * @return {{defineProperty: (function(*=, *=, *=): *), setPrototypeOf: (function(*, *=): boolean), set: (function(*, *, *, *): boolean), get: ((function(*=, *=, *=): (undefined))|*), deleteProperty: ((function(*, *): (boolean))|*)}} * @private * @see {@link https://gitlab.schukai.com/-/snippets/49} */ function getHandler() { const proxy = this; // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots const handler = { // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver get: function (target, key, receiver) { // this is an internal hack to identify proxy if (key === proxyInstanceMarker) { return proxyInstanceMarker; } const value = Reflect.get(target, key, receiver); if (typeof key === "symbol") { return value; } if (isPrimitive(value)) { return value; } // set value as proxy if object or array if (isArray(value) || isObject(value)) { if (proxy.objectMap.has(value)) { return proxy.objectMap.get(value); } else if (proxy.proxyMap.has(value)) { return value; } else { const p = new Proxy(value, handler); proxy.objectMap.set(value, p); proxy.proxyMap.set(p, value); return p; } } return value; }, // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver set: function (target, key, value, receiver) { if (proxy.proxyMap.has(value)) { value = proxy.proxyMap.get(value); } if (proxy.proxyMap.has(target)) { target = proxy.proxyMap.get(target); } let current = Reflect.get(target, key, receiver); if (proxy.proxyMap.has(current)) { current = proxy.proxyMap.get(current); } if (current === value) { return true; } let result; let descriptor = Reflect.getOwnPropertyDescriptor(target, key); if (descriptor === undefined) { descriptor = { writable: true, enumerable: true, configurable: true, }; } descriptor["value"] = value; result = Reflect.defineProperty(target, key, descriptor); if (typeof key !== "symbol") { proxy.observers.notify(proxy); } return result; }, // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p deleteProperty: function (target, key) { if (key in target) { delete target[key]; if (typeof key !== "symbol") { proxy.observers.notify(proxy); } return true; } return false; }, // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc defineProperty: function (target, key, descriptor) { const result = Reflect.defineProperty(target, key, descriptor); if (typeof key !== "symbol") { proxy.observers.notify(proxy); } return result; }, // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v setPrototypeOf: function (target, key) { const result = Reflect.setPrototypeOf(object1, key); if (typeof key !== "symbol") { proxy.observers.notify(proxy); } return result; }, }; return handler; }