UNPKG

@politie/sherlock-proxy

Version:
317 lines (314 loc) 12.6 kB
import { __generator } from 'tslib'; import { utils, isDerivable, _internal, isSettableDerivable, lens } from '@politie/sherlock'; var IS_DERIVABLE_PROXY = Symbol('isDerivableProxy'); /** * Returns whether obj is a DerivableProxy. * * @param obj the object to test */ function isDerivableProxy(obj) { return obj[IS_DERIVABLE_PROXY] === true; } /** * A ProxyDescriptor must be used to create DerivableProxies. It can be used in two ways, either create a new descriptor and * change any implementation details (if needed) or create a subclass to extend the behavior. Use the {@link #$create} method * to create a DerivableProxy. * * Note that `this` in methods points to the created proxy, so only methods and properties that start with a $-sign can be accessed * without trouble. * * Note also that properties that start with two $-signs are cleared on $create. */ var ProxyDescriptor = /** @class */ (function () { function ProxyDescriptor() { this.$$derivable = undefined; } Object.defineProperty(ProxyDescriptor.prototype, "$derivable", { /** * The derivable that is the input to all default methods on the Proxy and the {@link #$value} property. */ get: function () { var pd = this.$proxyDescriptor; return pd.$$derivable || (pd.$$derivable = createDerivable(pd.$target, pd.$lens && pd.$lens())); }, enumerable: false, configurable: true }); Object.defineProperty(ProxyDescriptor.prototype, "$value", { /** * The current value of the DerivableProxy. Can be expensive to calculate. When the target is settable (is an Atom) then $value * is writable. */ get: function () { var pd = this.$proxyDescriptor; try { return pd.$derivable.get(); } catch (e) { // istanbul ignore next: for debug purposes throw Object.assign(new Error("error while getting ".concat(pd.$expression || '$value', ": ").concat(_internal.isError(e) && e.message)), { jse_cause: e }); } }, set: function (newValue) { var pd = this.$proxyDescriptor; var atom = pd.$derivable; var expression = pd.$expression; if (!isSettableDerivable(atom)) { throw new Error("".concat(expression || '$value', " is readonly")); } try { atom.set(newValue); } catch (e) { throw Object.assign(new Error("error while setting ".concat(expression || '$value', ": ").concat(_internal.isError(e) && e.message)), { jse_cause: e }); } }, enumerable: false, configurable: true }); Object.defineProperty(ProxyDescriptor.prototype, "$targetValue", { /** * The current value of the target Derivable that was used to create the DerivableProxy. */ get: function () { var pd = this.$proxyDescriptor; try { return pd.$target.get(); } catch (e) { // istanbul ignore next: for debug purposes throw Object.assign(new Error("error while getting ".concat(pd.$expression || '$targetValue', ": ").concat(_internal.isError(e) && e.message)), { jse_cause: e }); } }, set: function (newValue) { var pd = this.$proxyDescriptor; var atom = pd.$target; var expression = pd.$expression; if (!isSettableDerivable(atom)) { throw new Error("".concat(expression || '$targetValue', " is readonly")); } try { atom.set(newValue); } catch (e) { throw Object.assign(new Error("error while setting ".concat(expression || '$targetValue', ": ").concat(_internal.isError(e) && e.message)), { jse_cause: e }); } }, enumerable: false, configurable: true }); Object.defineProperty(ProxyDescriptor.prototype, "$proxyDescriptor", { /** * In methods of a ProxyDescriptor, `this` is bound to the Proxy Object. Therefore, only $-properties and $-methods can be * accessed safely. Use $proxyDescriptor to get access to the ProxyDescriptor Object to prevent the ProxyHandler from messing * with your logic. */ get: function () { return this; }, enumerable: false, configurable: true }); /** * Wrap a Derivable as DerivableProxy using this ProxyDescriptor. * * @param obj the object to wrap * @param expression the new expression to the created DerivableProxy * @param path the new path to the created DerivableProxy */ ProxyDescriptor.prototype.$create = function (obj, expression, path) { var descriptor = utils.clone(this.$proxyDescriptor); descriptor.$target = obj; Object.getOwnPropertyNames(descriptor) .filter(function (prop) { return prop.startsWith('$$'); }) .forEach(function (prop) { return descriptor[prop] = undefined; }); descriptor.$expression = expression; descriptor.$path = path; return new Proxy(descriptor, proxyHandler); }; /** * The $pluck method is the implementation of the pluck mechanism of DerivableProxy. Replace this method to change the * pluck behavior. It should return a DerivableProxy. * * @param prop the property to pluck of the wrapped derivable */ ProxyDescriptor.prototype.$pluck = function (prop) { var pd = this.$proxyDescriptor; return pd.$create(pd.$derivable.pluck(prop), extendExpression(pd.$expression, prop), extendPath(pd.$path, prop)); }; /** * The $pluckableKeys returns a list of properties that can be plucked from this object. Returned keys are guaranteed to * result in a usable DerivableProxy when used with $pluck. Is used for `for ... in` and `Object.keys(...)` logic. */ ProxyDescriptor.prototype.$pluckableKeys = function () { var value = this.$proxyDescriptor.$value; return typeof value === 'object' ? Reflect.ownKeys(value) : []; }; /** * Method that determines whether the current object is iterable and if so, how many elements it contains. During iteration * {@link #pluck} is called with indices up to but not including the result of `$length()`. */ ProxyDescriptor.prototype.$length = function () { var maybeArray = this.$proxyDescriptor.$targetValue; return Array.isArray(maybeArray) ? maybeArray.length : undefined; }; ProxyDescriptor.prototype.$and = function (other) { return this.$proxyDescriptor.$derivable.and(unwrapProxy(other)); }; ProxyDescriptor.prototype.$or = function (other) { return this.$proxyDescriptor.$derivable.or(unwrapProxy(other)); }; ProxyDescriptor.prototype.$not = function () { return this.$proxyDescriptor.$derivable.not(); }; ProxyDescriptor.prototype.$is = function (other) { return this.$proxyDescriptor.$derivable.is(unwrapProxy(other)); }; ProxyDescriptor.prototype.$derive = function () { var target = this.$proxyDescriptor.$derivable; return target.derive.apply(target, arguments); }; ProxyDescriptor.prototype.$react = function (reaction, options) { return this.$proxyDescriptor.$derivable.react(reaction, options); }; ProxyDescriptor.prototype.toJSON = function () { return this.$proxyDescriptor.$value; }; Object.defineProperty(ProxyDescriptor.prototype, Symbol.toStringTag, { get: function () { return 'DerivableProxy'; }, enumerable: false, configurable: true }); ProxyDescriptor.prototype[Symbol.iterator] = function () { var pd, length, expression, i; return __generator(this, function (_a) { switch (_a.label) { case 0: pd = this.$proxyDescriptor; length = pd.$length(); if (length === undefined) { expression = pd.$expression; throw Object.assign(new Error("".concat(expression || 'object', " is not iterable")), { value: pd.$value, expression: expression }); } i = 0; _a.label = 1; case 1: if (!(i < length)) return [3 /*break*/, 4]; return [4 /*yield*/, pd.$pluck(i)]; case 2: _a.sent(); _a.label = 3; case 3: i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }; Object.defineProperty(ProxyDescriptor.prototype, "length", { get: function () { return this.$proxyDescriptor.$length(); }, enumerable: false, configurable: true }); return ProxyDescriptor; }()); ProxyDescriptor.prototype[IS_DERIVABLE_PROXY] = true; function createDerivable(target, proxyLens) { if (!proxyLens) { return target; } var get = proxyLens.get, set = proxyLens.set; if (!set || !isSettableDerivable(target)) { return target.derive(get).autoCache(); } return lens({ get: get, set: function (newValue, targetValue) { target.set(set.call(this, newValue, targetValue)); } }, target).autoCache(); } function unwrapProxy(obj) { if (isDerivableProxy(obj)) { return obj.$derivable; } return obj; } var proxyHandler = { get: function (target, prop, receiver) { if (prop === '$proxyDescriptor') { return target; } if (isPluckableProperty(target, prop)) { return target.$pluck.call(receiver, prop); } return Reflect.get(target, prop, receiver); }, set: function (target, prop, newValue, receiver) { if (isPluckableProperty(target, prop)) { var plucked = target.$pluck.call(receiver, prop); if (newValue && isDerivableProxy(newValue)) { plucked.$targetValue = newValue.$targetValue; } else { plucked.$value = newValue && isDerivable(newValue) ? newValue.get() : newValue; } return true; } return Reflect.set(target, prop, newValue, receiver); }, has: function (target, prop) { if (prop === Symbol.iterator) { return target.$length() !== undefined; } return isPluckableProperty(target, prop); }, getOwnPropertyDescriptor: function (target, prop) { if (isPluckableProperty(target, prop)) { return { get: function () { return this[prop]; }, set: function (newValue) { this[prop] = newValue; }, configurable: true, enumerable: true, }; } return undefined; }, ownKeys: function (target) { return target.$pluckableKeys(); }, }; function isPluckableProperty(target, prop) { return typeof prop === 'number' || typeof prop === 'string' && prop[0] !== '$' && !Reflect.has(target, prop); } /** * Extends an expression with a property access. Automatically uses bracket notation where appropriate and escapes * strings in brackets to give a realistic combined expression. * * @param expression the (optional) expression to extend * @param property the property that should be appended to the expression */ function extendExpression(expression, property) { if (expression === void 0) { expression = ''; } if (typeof property === 'string' && /^[a-z_][a-z_0-9]*$/i.test(property)) { return expression + '.' + property; } if (typeof property === 'string') { return expression + '["' + property.replace(/\\/g, '\\\\').replace(/\"/g, '\\"') + '"]'; } return expression + '[' + property + ']'; } /** * Extends a path with a property access. * * @param path the (optional) path to extend * @param property the property that should be appended to the path */ function extendPath(path, property) { if (path === void 0) { path = []; } return path.concat(property); } export { ProxyDescriptor, extendExpression, extendPath, isDerivableProxy, unwrapProxy }; //# sourceMappingURL=sherlock-proxy.esm.js.map