Skip to content

Commit

Permalink
test(E): check and document the EProxy
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Oct 12, 2019
1 parent 29ac5a4 commit 29d435b
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 16 deletions.
9 changes: 2 additions & 7 deletions src/E.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* global globalThis window */
// eslint-disable-next-line spaced-comment
/// <reference path="index.d.ts" />
// Shim globalThis when we don't have it.
if (typeof globalThis === 'undefined') {
const myGlobal = typeof window === 'undefined' ? global : window;
Expand Down Expand Up @@ -44,13 +46,6 @@ function EProxyHandler(x, HandledPromise) {

export default function makeE(HandledPromise) {
function E(x) {
// p = E(x).name(args)
//
// E(x) returns a proxy on which you can call arbitrary methods. Each of
// these method calls returns a promise. The method will be invoked on
// whatever 'x' designates (or resolves to) in a future turn, not this
// one.

const handler = EProxyHandler(x, HandledPromise);
return harden(new Proxy({}, handler));
}
Expand Down
105 changes: 96 additions & 9 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,106 @@
// Type definitions for eventual-send
// TODO: Add jsdocs.

type Property = string | number | symbol;

interface HandledPromiseConstructor {
prototype: Promise<unknown>;
applyFunction(target: unknown, args: unknown[]): Promise<unknown>;
applyFunctionSendOnly(target: unknown, args: unknown[]): void;
applyMethod(target: unknown, prop: Property, args: unknown[]): Promise<unknown>;
applyMethodSendOnly(target: unknown, prop: Property, args: unknown[]): void;
delete(target: unknown, prop: Property): Promise<boolean>;
deleteSendOnly(target: unknown, prop: Property): void;
get(target: unknown, prop: Property): Promise<unknown>;
getSendOnly(target: unknown, prop: Property): void;
has(target: unknown, prop: Property): Promise<boolean>;
hasSendOnly(target: unknown, prop: Property): void;
set<T = unknown>(target: unknown, prop: Property, value: T): Promise<T>;
setSendOnly(target: unknown, prop: Property, value: unknown): void;
}

declare const HandledPromise: HandledPromiseConstructor;

interface ESingleMethod<U> {
[prop: Property]: (...args) => U;
}

interface EChain<T = unknown> {
M: EChainMethod<EChain<T>>;
G: EChainGet<EChain<T>>;
S: EChainSet<EChain<T>>;
D: EChainDelete<EChain<boolean>>;
P: Promise<T>;
sendOnly: EChainSendOnly;
}

interface EChainSendOnly {
M: EChainMethod<void>;
G: EChainGet<void>;
S: EChainSet<void>;
D: EChainDelete<void>;
}

interface EChainMethod<U> {
(...args: unknown[]): U;
[prop: Property]: (...args: unknown) => U;
}

interface EChainGet<U> {
[prop: Property]: U;
}

interface EChainSet<U> {
/**
* Eventually set the prop property.
*/
[prop: Property]: (value: unknown) => U;
}

interface EChainDelete<U> {
/**
* Eventually delete the prop property.
*/
[prop: Property]: U is void ? U : EChain<boolean>;
}

interface EProxy {
/**
* E(x) returns a proxy on which you can call arbitrary methods. Each of
* these method calls returns a promise. The method will be invoked on
* whatever 'x' designates (or resolves to) in a future turn, not this
* one.
*
* @param {*} x target for method call
* @returns {ESingleMethod} method call proxy
*/
(x: unknown): ESingleMethod<Promise<unknown>>;
sendOnly: (x: unknown) => ESingleMethod<void>;
/**
* E.C(x) returns a chain where operations are selected by
* uppercase single-letter selectors.
*
* @param {*} x target for first operation
* @returns {EChain}
*/
C(x: unknown): EChain;
}

export const E: EProxy;

interface EHandler {
GET(p: EPromise<unknown>, name: string | number | symbol): EPromise<unknown>;
PUT(p: EPromise<unknown>, name: string | number | symbol, value: unknown): EPromise<void>;
DELETE(p: EPromise<unknown>, name: string | number | symbol): EPromise<boolean>;
POST(p: EPromise<unknown>, name?: string | number | symbol, args: unknown[]): EPromise<unknown>;
GET(p: EPromise<unknown>, name: Property): EPromise<unknown>;
PUT(p: EPromise<unknown>, name: Property, value: unknown): EPromise<void>;
DELETE(p: EPromise<unknown>, name: Property): EPromise<boolean>;
POST(p: EPromise<unknown>, name?: Property, args: unknown[]): EPromise<unknown>;
}

export interface EPromise<R> extends Promise<R> {
get(name: string | number | symbol): EPromise<unknown>;
put(name: string | number | symbol, value: unknown): EPromise<void>;
delete(name: string | number | symbol): EPromise<boolean>;
post(name?: string | number | symbol, args: unknown[]): EPromise<unknown>;
invoke(name: string | number | symbol, ...args: unknown[]): EPromise<unknown>;
get(name: Property): EPromise<unknown>;
put(name: Property, value: unknown): EPromise<void>;
delete(name: Property): EPromise<boolean>;
post(name?: Property, args: unknown[]): EPromise<unknown>;
invoke(name: Property, ...args: unknown[]): EPromise<unknown>;
fapply(args: unknown[]): EPromise<unknown>;
fcall(...args: unknown[]): EPromise<unknown>;
then<TResult1 = R, TResult2 = never>(
Expand Down
51 changes: 51 additions & 0 deletions test/test-e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import test from 'tape-promise/tape';
import { E } from '../src/index';

test('E method calls', async t => {
try {
const x = {
double(n) {
return 2 * n;
},
};
const d = E(x).double(6);
t.equal(typeof d.then, 'function', 'return is a thenable');
t.equal(await d, 12, 'method call works');
} catch (e) {
t.isNot(e, e, 'unexpected exception');
} finally {
t.end();
}
});

test.only('E.C chains', 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}!`;
},
};
const xC = E.C(x);
t.equal(await xC.M.hello('Hello').P, 'Hello, buddy!', 'method call works');
// console.log(await xC.G.y.G.fn.P);
// t.equal(await xC.G.y.G.fn.M(4).P, 8, 'anonymous method works');
t.equal(await xC.G.val.P, 123, 'property get');
t.equal(await xC.S.val(999).P, 999, 'property set');
t.equal(x.val, 999, 'property set works');
t.equal(await xC.D.val.P, true, 'property delete');
t.equal(x.val, undefined, 'delete worked');
await t.rejects(xC.G.y.D.val2.P, TypeError, 'property delete fails');
t.equal(x.y.val2, 456, 'delete failed');
} catch (e) {
t.isNot(e, e, 'unexpected exception');
} finally {
t.end();
}
});

0 comments on commit 29d435b

Please sign in to comment.