diff --git a/src/E.js b/src/E.js index 5dfee32b0f1..89bf3855f79 100644 --- a/src/E.js +++ b/src/E.js @@ -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). * @@ -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; @@ -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. @@ -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); diff --git a/src/index.js b/src/index.js index 616490998d6..984a0bff8fa 100644 --- a/src/index.js +++ b/src/index.js @@ -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); }, diff --git a/test/test-e.js b/test/test-e.js index 739c2d4d0f5..45fcd789fea 100644 --- a/test/test-e.js +++ b/test/test-e.js @@ -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 = {