Skip to content

Commit

Permalink
feat(E): new shortcuts to avoid full chaining
Browse files Browse the repository at this point in the history
The shortcuts all generate Promises, and are:

E.G(x).vname := get vname
E.D(x).vname := delete vname
E.S(x).vname(val) := set vname to val
E.M(x).method(...args) := invoke method(...args)
E.M(x)(...args) := invoke anonymous function(...args)

Now E(x) is shorthand for E.M(x)
  • Loading branch information
michaelfig committed Oct 17, 2019
1 parent 758e298 commit ebd7e2e
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 66 deletions.
149 changes: 91 additions & 58 deletions src/E.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ if (typeof globalThis === 'undefined') {

const harden = (globalThis.SES && globalThis.SES.harden) || Object.freeze;

const readOnlyProxy = {
set(_target, _prop, _value) {
return false;
},
isExtensible(_target) {
return false;
},
setPrototypeOf(_target, _value) {
return false;
},
deleteProperty(_target, _prop) {
return false;
},
};

/**
* A Proxy handler for E(x).
*
Expand All @@ -17,6 +32,7 @@ const harden = (globalThis.SES && globalThis.SES.harden) || Object.freeze;
*/
function EProxyHandler(x, HandledPromise) {
return harden({
...readOnlyProxy,
get(_target, p, _receiver) {
if (`${p}` !== p) {
return undefined;
Expand All @@ -28,14 +44,8 @@ function EProxyHandler(x, HandledPromise) {
// #95 for details.
return (...args) => harden(HandledPromise.applyMethod(x, p, args));
},
deleteProperty(_target, p) {
return harden(HandledPromise.delete(x, p));
},
set(_target, p, value, _receiver) {
return harden(HandledPromise.set(x, p, value));
},
apply(_target, _thisArg, argArray = []) {
return harden(HandledPromise.apply(x, argArray));
return harden(HandledPromise.applyFunction(x, argArray));
},
has(_target, _p) {
// We just pretend everything exists.
Expand All @@ -50,74 +60,97 @@ export default function makeE(HandledPromise) {
return harden(new Proxy({}, handler));
}

const EChain = x => {
return harden({
const makeEGetterProxy = (x, wrap = o => o) =>
new Proxy(
Object.create(null),
{
...readOnlyProxy,
has(_target, _prop) {
return true;
},
get(_target, prop) {
return wrap(HandledPromise.get(x, prop));
},
},
);


const makeEDeleterProxy = (x, wrap = o => o) =>
new Proxy(
Object.create(null),
{
...readOnlyProxy,
has(_target, _prop) {
return true;
},
get(_target, prop) {
return wrap(HandledPromise.delete(x, prop));
},
},
);

const makeESetterProxy = (x, wrap = o => o) =>
new Proxy(
Object.create(null),
{
...readOnlyProxy,
has(_target, _prop) {
return true;
},
get(_target, prop) {
return harden(value =>
wrap(HandledPromise.set(x, prop, value)),
);
},
},
);

const makeEMethodProxy = (x, wrap = o => o) =>
new Proxy(
(..._args) => {},
{
...readOnlyProxy,
has(_target, _prop) {
return true;
},
get(_target, prop) {
return harden((...args) =>
wrap(HandledPromise.applyMethod(x, prop, args)),
);
},
apply(_target, _thisArg, args = []) {
return wrap(HandledPromise.applyFunction(x, args));
},
});

E.G = makeEGetterProxy;
E.D = makeEDeleterProxy;
E.S = makeESetterProxy;
E.M = makeEMethodProxy;

const EChain = x =>
harden({
get G() {
// Return getter.
return new Proxy(
{ EChain: 'getter' },
{
has(_target, _prop) {
return true;
},
get(_target, prop) {
return EChain(HandledPromise.get(x, prop));
},
},
);
return makeEGetterProxy(x, EChain);
},
get D() {
// Return deleter.
return new Proxy(
{ EChain: 'deleter' },
{
has(_target, _prop) {
return true;
},
get(_target, prop) {
return EChain(HandledPromise.delete(x, prop));
},
},
);
return makeEDeleterProxy(x, EChain);
},
get S() {
// Return setter.
return new Proxy(
{ EChain: 'setter' },
{
has(_target, _prop) {
return true;
},
get(_target, prop) {
return harden(value =>
EChain(HandledPromise.set(x, prop, value)),
);
},
},
);
return makeESetterProxy(x, EChain);
},
get M() {
// Return method-caller.
return new Proxy((..._args) => {}, {
has(_target, _prop) {
return true;
},
get(_target, prop) {
return harden((...args) =>
EChain(HandledPromise.applyMethod(x, prop, args)),
);
},
apply(_target, _thisArg, args = []) {
return EChain(HandledPromise.applyFunction(x, args));
},
});
return makeEMethodProxy(x, EChain);
},
get P() {
// Return as promise.
return Promise.resolve(x);
},
});
};

E.C = EChain;
return harden(E);
Expand Down
8 changes: 0 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@ export function makeHandledPromise(EPromise) {
deleteSendOnly(target, key) {
EPromise.resolve(target).delete(key);
},
// TODO: Remove when making HandledPromise a constructor
// to avoid conflict with Function.prototype.apply
apply(target, args) {
return EPromise.resolve(target).post(undefined, args);
},
applySendOnly(target, args) {
EPromise.resolve(target).post(undefined, args);
},
applyFunction(target, args) {
return EPromise.resolve(target).post(undefined, args);
},
Expand Down
30 changes: 30 additions & 0 deletions test/test-e.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ test('E method calls', async t => {
}
});

test('E.* shortcuts', async t => {
try {
const x = {
name: 'buddy',
val: 123,
y: Object.freeze({
val2: 456,
name2: 'holly',
fn: n => 2 * n,
}),
hello(greeting) {
return `${greeting}, ${this.name}!`;
},
};
t.equal(await E.M(x).hello('Hello'), 'Hello, buddy!', 'method call works');
t.equal(await E.M(await E.G(await E.G(x).y).fn)(4), 8, 'anonymous method works');
t.equal(await E.G(x).val, 123, 'property get');
t.equal(await E.S(x).val(999), 999, 'property set');
t.equal(x.val, 999, 'property set works');
t.equal(await E.D(x).val, true, 'property delete');
t.equal(x.val, undefined, 'delete worked');
await t.rejects(E.D(await E.G(x).y).val2, TypeError, 'property delete fails');
t.equal(x.y.val2, 456, 'delete failed');
} catch (e) {
t.isNot(e, e, 'unexpected exception');
} finally {
t.end();
}
});

test('E.C chains', async t => {
try {
const x = {
Expand Down

0 comments on commit ebd7e2e

Please sign in to comment.