@politie/sherlock-proxy
Version:
A proxy extension to Sherlock.
317 lines (314 loc) • 12.6 kB
JavaScript
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