Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/transform-bang

Package Overview
Dependencies
Maintainers
4
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/transform-bang - npm Package Compare versions

Comparing version 0.0.1 to 0.1.0

472

dist/transform-bang.cjs.js
'use strict';
/**
* Create an EPromise class that supports eventual send (infix-bang) operations.
* This is a class that extends the BasePromise class argument (which may be platform
* Promises, or some other implementation). Only the `new BasePromise(executor)`
* constructor form is used directly by EPromise.
* Modify a Promise class to have it support eventual send (infix-bang) operations.
*

@@ -14,208 +11,203 @@ * Based heavily on nanoq https://github.com/drses/nanoq/blob/master/src/nanoq.js

*
* @param {typeof Promise} BasePromise ES6 Promise contstructor
* @returns {typeof EPromise} EPromise class
* @param {typeof Promise} Promise ES6 Promise class to shim
* @return {typeof EPromise} Extended promise
*/
function makeEPromiseClass(BasePromise) {
const presenceToResolvedRelay = new WeakMap();
const promiseToRelay = new WeakMap();
function maybeExtendPromise(Promise) {
// Make idempotent, so we don't layer on top of a BasePromise that is adequate.
let needsShim = false;
for (const method of [
'get',
'put',
'post',
'delete',
'invoke',
'fapply',
'fcall',
]) {
if (typeof Promise.prototype[method] !== 'function') {
needsShim = true;
break;
}
}
if (!needsShim) {
// Already supports all the methods.
return Promise;
}
// This special relay accepts Promises, and forwards
// the remote to its corresponding resolvedRelay.
const presenceToHandler = new WeakMap();
const presenceToPromise = new WeakMap();
const promiseToHandler = new WeakMap();
// This special handler accepts Promises, and forwards
// handled Promises to their corresponding resolvedHandler.
//
// If passed a Promise that is not remote, perform
// If passed a Promise that is not handled, perform
// the corresponding local operation.
let forwardingRelay;
let forwardingHandler;
function relay(p) {
return promiseToRelay.get(p) || forwardingRelay;
return promiseToHandler.get(p) || forwardingHandler;
}
class EPromise extends BasePromise {
static makeRemote(executor, unresolvedRelay) {
let remoteResolve;
let remoteReject;
let relayResolve;
const remoteP = new EPromise((resolve, reject) => {
remoteResolve = resolve;
remoteReject = reject;
});
Object.defineProperties(
Promise.prototype,
Object.getOwnPropertyDescriptors({
get(key) {
return relay(this).GET(this, key);
},
if (!unresolvedRelay) {
// Create a simple unresolvedRelay that just postpones until the
// resolvedRelay is set.
//
// This is insufficient for actual remote Promises (too many round-trips),
// but is an easy way to create a local Remote.
const relayP = new EPromise(resolve => {
relayResolve = resolve;
});
put(key, val) {
return relay(this).PUT(this, key, val);
},
const postpone = forwardedOperation => {
// Just wait until the relay is resolved/rejected.
return async (p, ...args) => {
console.log(`forwarding ${forwardedOperation}`);
await relayP;
return p[forwardedOperation](args);
};
};
delete(key) {
return relay(this).DELETE(this, key);
},
unresolvedRelay = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
},
// Until the remote is resolved, we use the unresolvedRelay.
promiseToRelay.set(remoteP, unresolvedRelay);
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
},
function rejectRemote(reason) {
if (relayResolve) {
relayResolve(null);
}
remoteReject(reason);
}
fapply(args) {
return relay(this).POST(this, undefined, args);
},
function resolveRemote(presence, resolvedRelay) {
try {
if (resolvedRelay) {
// Sanity checks.
if (Object(resolvedRelay) !== resolvedRelay) {
throw TypeError(
`Resolved relay ${resolvedRelay} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedRelay[method] !== 'function') {
throw TypeError(
`Resolved relay ${resolvedRelay} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToResolvedRelay.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
fcall(...args) {
return relay(this).POST(this, undefined, args);
},
}),
);
// Create a table entry for the presence mapped to the resolvedRelay.
presenceToResolvedRelay.set(presence, resolvedRelay);
}
const baseResolve = Promise.resolve.bind(Promise);
// Remove the mapping, as our resolvedRelay should be used instead.
promiseToRelay.delete(remoteP);
// Resolve with the new presence or other value.
remoteResolve(presence);
if (relayResolve) {
// Activate the default unresolvedRelay.
relayResolve(resolvedRelay);
}
} catch (e) {
rejectRemote(e);
// Add Promise.makeHandled and update Promise.resolve.
Object.defineProperties(
Promise,
Object.getOwnPropertyDescriptors({
resolve(value) {
// Resolving a Presence returns the pre-registered handled promise.
const handledPromise = presenceToPromise.get(value);
if (handledPromise) {
return handledPromise;
}
}
return baseResolve(value);
},
// Invoke the callback to let the user resolve/reject.
executor(resolveRemote, rejectRemote);
makeHandled(executor, unresolvedHandler) {
let handledResolve;
let handledReject;
let resolveTheHandler;
const handledP = new Promise((resolve, reject) => {
handledResolve = resolve;
handledReject = reject;
});
// Return a remote EPromise, which wil be resolved/rejected
// by the executor.
return remoteP;
}
if (!unresolvedHandler) {
// Create a simple unresolvedHandler that just postpones until the
// resolvedHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a local handled Promise.
const handlerP = new Promise(resolve => {
resolveTheHandler = resolve;
});
static resolve(value) {
return new EPromise(resolve => resolve(value));
}
const postpone = forwardedOperation => {
// Just wait until the handler is resolved/rejected.
return async (p, ...args) => {
// console.log(`forwarding ${forwardedOperation}`);
await handlerP;
return p[forwardedOperation](args);
};
};
static reject(reason) {
return new EPromise((_resolve, reject) => reject(reason));
}
unresolvedHandler = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
get(key) {
return relay(this).GET(this, key);
}
// Until the handled promise is resolved, we use the unresolvedHandler.
promiseToHandler.set(handledP, unresolvedHandler);
put(key, val) {
return relay(this).PUT(this, key, val);
}
function rejectHandled(reason) {
if (resolveTheHandler) {
resolveTheHandler(null);
}
handledReject(reason);
}
delete(key) {
return relay(this).DELETE(this, key);
}
function resolveHandled(presence, resolvedHandler) {
try {
if (resolvedHandler) {
// Sanity checks.
if (Object(resolvedHandler) !== resolvedHandler) {
throw TypeError(
`Resolved handler ${resolvedHandler} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedHandler[method] !== 'function') {
throw TypeError(
`Resolved handler ${resolvedHandler} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToHandler.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
}
// Create table entries for the presence mapped to the resolvedHandler.
presenceToPromise.set(presence, handledP);
presenceToHandler.set(presence, resolvedHandler);
}
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
}
// Remove the mapping, as our resolvedHandler should be used instead.
promiseToHandler.delete(handledP);
fapply(args) {
return relay(this).POST(this, undefined, args);
}
// Resolve with the new presence or other value.
handledResolve(presence);
fcall(...args) {
return relay(this).POST(this, undefined, args);
}
// ***********************************************************
// The rest of these static methods ensure we use the correct
// EPromise.resolve and EPromise.reject, no matter what the
// implementation of the inherited BasePromise is.
static all(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Reject if any reject.
if (item.status === 'rejected') {
throw item.reason;
if (resolveTheHandler) {
// Activate the default unresolvedHandler.
resolveTheHandler(resolvedHandler);
}
} catch (e) {
rejectHandled(e);
}
}
// Add the resolved value to the array.
res[index] = item.value;
// Invoke the callback to let the user resolve/reject.
executor(resolveHandled, rejectHandled);
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// Return a handled Promise, which wil be resolved/rejected
// by the executor.
return handledP;
},
}),
);
static allSettled(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Add the reified promise result to the array.
res[index] = item;
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// TODO: Implement any(iterable) according to spec.
// Also add it to the SES/Jessie whitelists.
static race(iterable) {
// Just return the first reified promise result, whether fulfilled or rejected.
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (_, item) => item);
}
}
function makeForwarder(operation, localImpl) {
return async (ep, ...args) => {
const o = await ep;
const resolvedRelay = presenceToResolvedRelay.get(o);
if (resolvedRelay) {
// The relay was resolved, so give it a naked object.
return resolvedRelay[operation](o, ...args);
const resolvedHandler = presenceToHandler.get(o);
if (resolvedHandler) {
// The handler was resolved, so give it a naked object.
return resolvedHandler[operation](o, ...args);
}

@@ -229,3 +221,3 @@

forwardingRelay = {
forwardingHandler = {
GET: makeForwarder('GET', (o, key) => o[key]),

@@ -241,129 +233,13 @@ PUT: makeForwarder('PUT', (o, key, val) => (o[key] = val)),

};
return Promise;
}
/**
* Reduce-like helper function to support iterable values mapped to Promise.resolve,
* and combine them asynchronously.
*
* The combiner may be called in any order, and the collection is not necessarily
* done iterating by the time it's called.
*
* The notable difference from reduce is that the combiner gets a reified
* settled promise as its `item` argument, and returns a combiner action
* with a `status` field of "rejected", "fulfilled", or "continue".
*
* @param {*} initValue first value of result
* @param {Iterable} iterable values to EPromise.resolve
* @param {Combiner} combiner synchronously reduce each item
* @returns {EPromise<*>}
*/
function combinePromises(initValue, iterable, combiner) {
let result = initValue;
const EPromise = maybeExtendPromise(Promise);
// We use the platform async keyword here to simplify
// the executor function.
return new EPromise(async (resolve, reject) => {
// We start at 1 to prevent the iterator from resolving
// the EPromise until the loop is complete and all items
// have been reduced.
let countDown = 1;
let alreadySettled = false;
// Attempt to patch the platform.
(typeof window === 'undefined' ? global : window).Promise = EPromise;
function rejectOnce(e) {
if (!alreadySettled) {
alreadySettled = true;
reject(e);
}
}
function resolveOnce(value) {
if (!alreadySettled) {
alreadySettled = true;
resolve(value);
}
}
function doCountDown() {
countDown -= 1;
if (countDown === 0) {
// Resolve the outer promise.
resolveOnce(result);
}
}
async function doCombine(mapped, index) {
if (alreadySettled) {
// Short-circuit out of here, since we already
// rejected or resolved.
return;
}
// Either update the result or throw an exception.
const action = await combiner(result, mapped, index);
switch (action.status) {
case 'continue':
// eslint-disable-next-line prefer-destructuring
result = action.result;
break;
case 'rejected':
rejectOnce(action.reason);
break;
case 'fulfilled':
// Resolve the outer promise.
result = action.value;
resolveOnce(result);
break;
default:
throw TypeError(`Not a valid combiner return value: ${action}`);
}
doCountDown();
}
try {
let i = 0;
for (const item of iterable) {
const index = i;
i += 1;
// Say that we have one more to wait for.
countDown += 1;
EPromise.resolve(item)
.then(
value => doCombine({ status: 'fulfilled', value }, index), // Successful resolve.
reason => doCombine({ status: 'rejected', reason }, index), // Failed resolve.
)
.catch(rejectOnce);
}
// If we had no items or they all settled before the
// loop ended, this will count down to zero and resolve
// the result.
doCountDown();
} catch (e) {
rejectOnce(e);
}
});
}
return EPromise;
}
function makeBangTransformer(parse, generate) {
let EPromise;
const transform = {
init(r, harden) {
// Create a new EPromise from the platform Promise implementation.
EPromise = harden(r.evaluate(`(${makeEPromiseClass})`)(r.global.Promise));
},
endow(es) {
if (!EPromise) {
// Not yet initialized: don't override platform Promises.
return es;
}
// Override the global Promise reference.
return {

@@ -370,0 +246,0 @@ ...es,

/**
* Create an EPromise class that supports eventual send (infix-bang) operations.
* This is a class that extends the BasePromise class argument (which may be platform
* Promises, or some other implementation). Only the `new BasePromise(executor)`
* constructor form is used directly by EPromise.
* Modify a Promise class to have it support eventual send (infix-bang) operations.
*

@@ -12,208 +9,203 @@ * Based heavily on nanoq https://github.com/drses/nanoq/blob/master/src/nanoq.js

*
* @param {typeof Promise} BasePromise ES6 Promise contstructor
* @returns {typeof EPromise} EPromise class
* @param {typeof Promise} Promise ES6 Promise class to shim
* @return {typeof EPromise} Extended promise
*/
function makeEPromiseClass(BasePromise) {
const presenceToResolvedRelay = new WeakMap();
const promiseToRelay = new WeakMap();
function maybeExtendPromise(Promise) {
// Make idempotent, so we don't layer on top of a BasePromise that is adequate.
let needsShim = false;
for (const method of [
'get',
'put',
'post',
'delete',
'invoke',
'fapply',
'fcall',
]) {
if (typeof Promise.prototype[method] !== 'function') {
needsShim = true;
break;
}
}
if (!needsShim) {
// Already supports all the methods.
return Promise;
}
// This special relay accepts Promises, and forwards
// the remote to its corresponding resolvedRelay.
const presenceToHandler = new WeakMap();
const presenceToPromise = new WeakMap();
const promiseToHandler = new WeakMap();
// This special handler accepts Promises, and forwards
// handled Promises to their corresponding resolvedHandler.
//
// If passed a Promise that is not remote, perform
// If passed a Promise that is not handled, perform
// the corresponding local operation.
let forwardingRelay;
let forwardingHandler;
function relay(p) {
return promiseToRelay.get(p) || forwardingRelay;
return promiseToHandler.get(p) || forwardingHandler;
}
class EPromise extends BasePromise {
static makeRemote(executor, unresolvedRelay) {
let remoteResolve;
let remoteReject;
let relayResolve;
const remoteP = new EPromise((resolve, reject) => {
remoteResolve = resolve;
remoteReject = reject;
});
Object.defineProperties(
Promise.prototype,
Object.getOwnPropertyDescriptors({
get(key) {
return relay(this).GET(this, key);
},
if (!unresolvedRelay) {
// Create a simple unresolvedRelay that just postpones until the
// resolvedRelay is set.
//
// This is insufficient for actual remote Promises (too many round-trips),
// but is an easy way to create a local Remote.
const relayP = new EPromise(resolve => {
relayResolve = resolve;
});
put(key, val) {
return relay(this).PUT(this, key, val);
},
const postpone = forwardedOperation => {
// Just wait until the relay is resolved/rejected.
return async (p, ...args) => {
console.log(`forwarding ${forwardedOperation}`);
await relayP;
return p[forwardedOperation](args);
};
};
delete(key) {
return relay(this).DELETE(this, key);
},
unresolvedRelay = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
},
// Until the remote is resolved, we use the unresolvedRelay.
promiseToRelay.set(remoteP, unresolvedRelay);
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
},
function rejectRemote(reason) {
if (relayResolve) {
relayResolve(null);
}
remoteReject(reason);
}
fapply(args) {
return relay(this).POST(this, undefined, args);
},
function resolveRemote(presence, resolvedRelay) {
try {
if (resolvedRelay) {
// Sanity checks.
if (Object(resolvedRelay) !== resolvedRelay) {
throw TypeError(
`Resolved relay ${resolvedRelay} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedRelay[method] !== 'function') {
throw TypeError(
`Resolved relay ${resolvedRelay} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToResolvedRelay.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
fcall(...args) {
return relay(this).POST(this, undefined, args);
},
}),
);
// Create a table entry for the presence mapped to the resolvedRelay.
presenceToResolvedRelay.set(presence, resolvedRelay);
}
const baseResolve = Promise.resolve.bind(Promise);
// Remove the mapping, as our resolvedRelay should be used instead.
promiseToRelay.delete(remoteP);
// Resolve with the new presence or other value.
remoteResolve(presence);
if (relayResolve) {
// Activate the default unresolvedRelay.
relayResolve(resolvedRelay);
}
} catch (e) {
rejectRemote(e);
// Add Promise.makeHandled and update Promise.resolve.
Object.defineProperties(
Promise,
Object.getOwnPropertyDescriptors({
resolve(value) {
// Resolving a Presence returns the pre-registered handled promise.
const handledPromise = presenceToPromise.get(value);
if (handledPromise) {
return handledPromise;
}
}
return baseResolve(value);
},
// Invoke the callback to let the user resolve/reject.
executor(resolveRemote, rejectRemote);
makeHandled(executor, unresolvedHandler) {
let handledResolve;
let handledReject;
let resolveTheHandler;
const handledP = new Promise((resolve, reject) => {
handledResolve = resolve;
handledReject = reject;
});
// Return a remote EPromise, which wil be resolved/rejected
// by the executor.
return remoteP;
}
if (!unresolvedHandler) {
// Create a simple unresolvedHandler that just postpones until the
// resolvedHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a local handled Promise.
const handlerP = new Promise(resolve => {
resolveTheHandler = resolve;
});
static resolve(value) {
return new EPromise(resolve => resolve(value));
}
const postpone = forwardedOperation => {
// Just wait until the handler is resolved/rejected.
return async (p, ...args) => {
// console.log(`forwarding ${forwardedOperation}`);
await handlerP;
return p[forwardedOperation](args);
};
};
static reject(reason) {
return new EPromise((_resolve, reject) => reject(reason));
}
unresolvedHandler = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
get(key) {
return relay(this).GET(this, key);
}
// Until the handled promise is resolved, we use the unresolvedHandler.
promiseToHandler.set(handledP, unresolvedHandler);
put(key, val) {
return relay(this).PUT(this, key, val);
}
function rejectHandled(reason) {
if (resolveTheHandler) {
resolveTheHandler(null);
}
handledReject(reason);
}
delete(key) {
return relay(this).DELETE(this, key);
}
function resolveHandled(presence, resolvedHandler) {
try {
if (resolvedHandler) {
// Sanity checks.
if (Object(resolvedHandler) !== resolvedHandler) {
throw TypeError(
`Resolved handler ${resolvedHandler} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedHandler[method] !== 'function') {
throw TypeError(
`Resolved handler ${resolvedHandler} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToHandler.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
}
// Create table entries for the presence mapped to the resolvedHandler.
presenceToPromise.set(presence, handledP);
presenceToHandler.set(presence, resolvedHandler);
}
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
}
// Remove the mapping, as our resolvedHandler should be used instead.
promiseToHandler.delete(handledP);
fapply(args) {
return relay(this).POST(this, undefined, args);
}
// Resolve with the new presence or other value.
handledResolve(presence);
fcall(...args) {
return relay(this).POST(this, undefined, args);
}
// ***********************************************************
// The rest of these static methods ensure we use the correct
// EPromise.resolve and EPromise.reject, no matter what the
// implementation of the inherited BasePromise is.
static all(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Reject if any reject.
if (item.status === 'rejected') {
throw item.reason;
if (resolveTheHandler) {
// Activate the default unresolvedHandler.
resolveTheHandler(resolvedHandler);
}
} catch (e) {
rejectHandled(e);
}
}
// Add the resolved value to the array.
res[index] = item.value;
// Invoke the callback to let the user resolve/reject.
executor(resolveHandled, rejectHandled);
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// Return a handled Promise, which wil be resolved/rejected
// by the executor.
return handledP;
},
}),
);
static allSettled(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Add the reified promise result to the array.
res[index] = item;
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// TODO: Implement any(iterable) according to spec.
// Also add it to the SES/Jessie whitelists.
static race(iterable) {
// Just return the first reified promise result, whether fulfilled or rejected.
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (_, item) => item);
}
}
function makeForwarder(operation, localImpl) {
return async (ep, ...args) => {
const o = await ep;
const resolvedRelay = presenceToResolvedRelay.get(o);
if (resolvedRelay) {
// The relay was resolved, so give it a naked object.
return resolvedRelay[operation](o, ...args);
const resolvedHandler = presenceToHandler.get(o);
if (resolvedHandler) {
// The handler was resolved, so give it a naked object.
return resolvedHandler[operation](o, ...args);
}

@@ -227,3 +219,3 @@

forwardingRelay = {
forwardingHandler = {
GET: makeForwarder('GET', (o, key) => o[key]),

@@ -239,129 +231,13 @@ PUT: makeForwarder('PUT', (o, key, val) => (o[key] = val)),

};
return Promise;
}
/**
* Reduce-like helper function to support iterable values mapped to Promise.resolve,
* and combine them asynchronously.
*
* The combiner may be called in any order, and the collection is not necessarily
* done iterating by the time it's called.
*
* The notable difference from reduce is that the combiner gets a reified
* settled promise as its `item` argument, and returns a combiner action
* with a `status` field of "rejected", "fulfilled", or "continue".
*
* @param {*} initValue first value of result
* @param {Iterable} iterable values to EPromise.resolve
* @param {Combiner} combiner synchronously reduce each item
* @returns {EPromise<*>}
*/
function combinePromises(initValue, iterable, combiner) {
let result = initValue;
const EPromise = maybeExtendPromise(Promise);
// We use the platform async keyword here to simplify
// the executor function.
return new EPromise(async (resolve, reject) => {
// We start at 1 to prevent the iterator from resolving
// the EPromise until the loop is complete and all items
// have been reduced.
let countDown = 1;
let alreadySettled = false;
// Attempt to patch the platform.
(typeof window === 'undefined' ? global : window).Promise = EPromise;
function rejectOnce(e) {
if (!alreadySettled) {
alreadySettled = true;
reject(e);
}
}
function resolveOnce(value) {
if (!alreadySettled) {
alreadySettled = true;
resolve(value);
}
}
function doCountDown() {
countDown -= 1;
if (countDown === 0) {
// Resolve the outer promise.
resolveOnce(result);
}
}
async function doCombine(mapped, index) {
if (alreadySettled) {
// Short-circuit out of here, since we already
// rejected or resolved.
return;
}
// Either update the result or throw an exception.
const action = await combiner(result, mapped, index);
switch (action.status) {
case 'continue':
// eslint-disable-next-line prefer-destructuring
result = action.result;
break;
case 'rejected':
rejectOnce(action.reason);
break;
case 'fulfilled':
// Resolve the outer promise.
result = action.value;
resolveOnce(result);
break;
default:
throw TypeError(`Not a valid combiner return value: ${action}`);
}
doCountDown();
}
try {
let i = 0;
for (const item of iterable) {
const index = i;
i += 1;
// Say that we have one more to wait for.
countDown += 1;
EPromise.resolve(item)
.then(
value => doCombine({ status: 'fulfilled', value }, index), // Successful resolve.
reason => doCombine({ status: 'rejected', reason }, index), // Failed resolve.
)
.catch(rejectOnce);
}
// If we had no items or they all settled before the
// loop ended, this will count down to zero and resolve
// the result.
doCountDown();
} catch (e) {
rejectOnce(e);
}
});
}
return EPromise;
}
function makeBangTransformer(parse, generate) {
let EPromise;
const transform = {
init(r, harden) {
// Create a new EPromise from the platform Promise implementation.
EPromise = harden(r.evaluate(`(${makeEPromiseClass})`)(r.global.Promise));
},
endow(es) {
if (!EPromise) {
// Not yet initialized: don't override platform Promises.
return es;
}
// Override the global Promise reference.
return {

@@ -368,0 +244,0 @@ ...es,

@@ -8,6 +8,3 @@ (function (global, factory) {

/**
* Create an EPromise class that supports eventual send (infix-bang) operations.
* This is a class that extends the BasePromise class argument (which may be platform
* Promises, or some other implementation). Only the `new BasePromise(executor)`
* constructor form is used directly by EPromise.
* Modify a Promise class to have it support eventual send (infix-bang) operations.
*

@@ -19,208 +16,203 @@ * Based heavily on nanoq https://github.com/drses/nanoq/blob/master/src/nanoq.js

*
* @param {typeof Promise} BasePromise ES6 Promise contstructor
* @returns {typeof EPromise} EPromise class
* @param {typeof Promise} Promise ES6 Promise class to shim
* @return {typeof EPromise} Extended promise
*/
function makeEPromiseClass(BasePromise) {
const presenceToResolvedRelay = new WeakMap();
const promiseToRelay = new WeakMap();
function maybeExtendPromise(Promise) {
// Make idempotent, so we don't layer on top of a BasePromise that is adequate.
let needsShim = false;
for (const method of [
'get',
'put',
'post',
'delete',
'invoke',
'fapply',
'fcall',
]) {
if (typeof Promise.prototype[method] !== 'function') {
needsShim = true;
break;
}
}
if (!needsShim) {
// Already supports all the methods.
return Promise;
}
// This special relay accepts Promises, and forwards
// the remote to its corresponding resolvedRelay.
const presenceToHandler = new WeakMap();
const presenceToPromise = new WeakMap();
const promiseToHandler = new WeakMap();
// This special handler accepts Promises, and forwards
// handled Promises to their corresponding resolvedHandler.
//
// If passed a Promise that is not remote, perform
// If passed a Promise that is not handled, perform
// the corresponding local operation.
let forwardingRelay;
let forwardingHandler;
function relay(p) {
return promiseToRelay.get(p) || forwardingRelay;
return promiseToHandler.get(p) || forwardingHandler;
}
class EPromise extends BasePromise {
static makeRemote(executor, unresolvedRelay) {
let remoteResolve;
let remoteReject;
let relayResolve;
const remoteP = new EPromise((resolve, reject) => {
remoteResolve = resolve;
remoteReject = reject;
});
Object.defineProperties(
Promise.prototype,
Object.getOwnPropertyDescriptors({
get(key) {
return relay(this).GET(this, key);
},
if (!unresolvedRelay) {
// Create a simple unresolvedRelay that just postpones until the
// resolvedRelay is set.
//
// This is insufficient for actual remote Promises (too many round-trips),
// but is an easy way to create a local Remote.
const relayP = new EPromise(resolve => {
relayResolve = resolve;
});
put(key, val) {
return relay(this).PUT(this, key, val);
},
const postpone = forwardedOperation => {
// Just wait until the relay is resolved/rejected.
return async (p, ...args) => {
console.log(`forwarding ${forwardedOperation}`);
await relayP;
return p[forwardedOperation](args);
};
};
delete(key) {
return relay(this).DELETE(this, key);
},
unresolvedRelay = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
},
// Until the remote is resolved, we use the unresolvedRelay.
promiseToRelay.set(remoteP, unresolvedRelay);
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
},
function rejectRemote(reason) {
if (relayResolve) {
relayResolve(null);
}
remoteReject(reason);
}
fapply(args) {
return relay(this).POST(this, undefined, args);
},
function resolveRemote(presence, resolvedRelay) {
try {
if (resolvedRelay) {
// Sanity checks.
if (Object(resolvedRelay) !== resolvedRelay) {
throw TypeError(
`Resolved relay ${resolvedRelay} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedRelay[method] !== 'function') {
throw TypeError(
`Resolved relay ${resolvedRelay} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToResolvedRelay.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
fcall(...args) {
return relay(this).POST(this, undefined, args);
},
}),
);
// Create a table entry for the presence mapped to the resolvedRelay.
presenceToResolvedRelay.set(presence, resolvedRelay);
}
const baseResolve = Promise.resolve.bind(Promise);
// Remove the mapping, as our resolvedRelay should be used instead.
promiseToRelay.delete(remoteP);
// Resolve with the new presence or other value.
remoteResolve(presence);
if (relayResolve) {
// Activate the default unresolvedRelay.
relayResolve(resolvedRelay);
}
} catch (e) {
rejectRemote(e);
// Add Promise.makeHandled and update Promise.resolve.
Object.defineProperties(
Promise,
Object.getOwnPropertyDescriptors({
resolve(value) {
// Resolving a Presence returns the pre-registered handled promise.
const handledPromise = presenceToPromise.get(value);
if (handledPromise) {
return handledPromise;
}
}
return baseResolve(value);
},
// Invoke the callback to let the user resolve/reject.
executor(resolveRemote, rejectRemote);
makeHandled(executor, unresolvedHandler) {
let handledResolve;
let handledReject;
let resolveTheHandler;
const handledP = new Promise((resolve, reject) => {
handledResolve = resolve;
handledReject = reject;
});
// Return a remote EPromise, which wil be resolved/rejected
// by the executor.
return remoteP;
}
if (!unresolvedHandler) {
// Create a simple unresolvedHandler that just postpones until the
// resolvedHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a local handled Promise.
const handlerP = new Promise(resolve => {
resolveTheHandler = resolve;
});
static resolve(value) {
return new EPromise(resolve => resolve(value));
}
const postpone = forwardedOperation => {
// Just wait until the handler is resolved/rejected.
return async (p, ...args) => {
// console.log(`forwarding ${forwardedOperation}`);
await handlerP;
return p[forwardedOperation](args);
};
};
static reject(reason) {
return new EPromise((_resolve, reject) => reject(reason));
}
unresolvedHandler = {
GET: postpone('get'),
PUT: postpone('put'),
DELETE: postpone('delete'),
POST: postpone('post'),
};
}
get(key) {
return relay(this).GET(this, key);
}
// Until the handled promise is resolved, we use the unresolvedHandler.
promiseToHandler.set(handledP, unresolvedHandler);
put(key, val) {
return relay(this).PUT(this, key, val);
}
function rejectHandled(reason) {
if (resolveTheHandler) {
resolveTheHandler(null);
}
handledReject(reason);
}
delete(key) {
return relay(this).DELETE(this, key);
}
function resolveHandled(presence, resolvedHandler) {
try {
if (resolvedHandler) {
// Sanity checks.
if (Object(resolvedHandler) !== resolvedHandler) {
throw TypeError(
`Resolved handler ${resolvedHandler} cannot be a primitive`,
);
}
for (const method of ['GET', 'PUT', 'DELETE', 'POST']) {
if (typeof resolvedHandler[method] !== 'function') {
throw TypeError(
`Resolved handler ${resolvedHandler} requires a ${method} method`,
);
}
}
if (Object(presence) !== presence) {
throw TypeError(`Presence ${presence} cannot be a primitive`);
}
if (presence === null) {
throw TypeError(`Presence ${presence} cannot be null`);
}
if (presenceToHandler.has(presence)) {
throw TypeError(`Presence ${presence} is already mapped`);
}
if (presence && typeof presence.then === 'function') {
throw TypeError(
`Presence ${presence} cannot be a Promise or other thenable`,
);
}
post(optKey, args) {
return relay(this).POST(this, optKey, args);
}
// Create table entries for the presence mapped to the resolvedHandler.
presenceToPromise.set(presence, handledP);
presenceToHandler.set(presence, resolvedHandler);
}
invoke(optKey, ...args) {
return relay(this).POST(this, optKey, args);
}
// Remove the mapping, as our resolvedHandler should be used instead.
promiseToHandler.delete(handledP);
fapply(args) {
return relay(this).POST(this, undefined, args);
}
// Resolve with the new presence or other value.
handledResolve(presence);
fcall(...args) {
return relay(this).POST(this, undefined, args);
}
// ***********************************************************
// The rest of these static methods ensure we use the correct
// EPromise.resolve and EPromise.reject, no matter what the
// implementation of the inherited BasePromise is.
static all(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Reject if any reject.
if (item.status === 'rejected') {
throw item.reason;
if (resolveTheHandler) {
// Activate the default unresolvedHandler.
resolveTheHandler(resolvedHandler);
}
} catch (e) {
rejectHandled(e);
}
}
// Add the resolved value to the array.
res[index] = item.value;
// Invoke the callback to let the user resolve/reject.
executor(resolveHandled, rejectHandled);
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// Return a handled Promise, which wil be resolved/rejected
// by the executor.
return handledP;
},
}),
);
static allSettled(iterable) {
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (res, item, index) => {
// Add the reified promise result to the array.
res[index] = item;
// Continue combining promise results.
return { status: 'continue', result: res };
});
}
// TODO: Implement any(iterable) according to spec.
// Also add it to the SES/Jessie whitelists.
static race(iterable) {
// Just return the first reified promise result, whether fulfilled or rejected.
// eslint-disable-next-line no-use-before-define
return combinePromises([], iterable, (_, item) => item);
}
}
function makeForwarder(operation, localImpl) {
return async (ep, ...args) => {
const o = await ep;
const resolvedRelay = presenceToResolvedRelay.get(o);
if (resolvedRelay) {
// The relay was resolved, so give it a naked object.
return resolvedRelay[operation](o, ...args);
const resolvedHandler = presenceToHandler.get(o);
if (resolvedHandler) {
// The handler was resolved, so give it a naked object.
return resolvedHandler[operation](o, ...args);
}

@@ -234,3 +226,3 @@

forwardingRelay = {
forwardingHandler = {
GET: makeForwarder('GET', (o, key) => o[key]),

@@ -246,129 +238,13 @@ PUT: makeForwarder('PUT', (o, key, val) => (o[key] = val)),

};
return Promise;
}
/**
* Reduce-like helper function to support iterable values mapped to Promise.resolve,
* and combine them asynchronously.
*
* The combiner may be called in any order, and the collection is not necessarily
* done iterating by the time it's called.
*
* The notable difference from reduce is that the combiner gets a reified
* settled promise as its `item` argument, and returns a combiner action
* with a `status` field of "rejected", "fulfilled", or "continue".
*
* @param {*} initValue first value of result
* @param {Iterable} iterable values to EPromise.resolve
* @param {Combiner} combiner synchronously reduce each item
* @returns {EPromise<*>}
*/
function combinePromises(initValue, iterable, combiner) {
let result = initValue;
const EPromise = maybeExtendPromise(Promise);
// We use the platform async keyword here to simplify
// the executor function.
return new EPromise(async (resolve, reject) => {
// We start at 1 to prevent the iterator from resolving
// the EPromise until the loop is complete and all items
// have been reduced.
let countDown = 1;
let alreadySettled = false;
// Attempt to patch the platform.
(typeof window === 'undefined' ? global : window).Promise = EPromise;
function rejectOnce(e) {
if (!alreadySettled) {
alreadySettled = true;
reject(e);
}
}
function resolveOnce(value) {
if (!alreadySettled) {
alreadySettled = true;
resolve(value);
}
}
function doCountDown() {
countDown -= 1;
if (countDown === 0) {
// Resolve the outer promise.
resolveOnce(result);
}
}
async function doCombine(mapped, index) {
if (alreadySettled) {
// Short-circuit out of here, since we already
// rejected or resolved.
return;
}
// Either update the result or throw an exception.
const action = await combiner(result, mapped, index);
switch (action.status) {
case 'continue':
// eslint-disable-next-line prefer-destructuring
result = action.result;
break;
case 'rejected':
rejectOnce(action.reason);
break;
case 'fulfilled':
// Resolve the outer promise.
result = action.value;
resolveOnce(result);
break;
default:
throw TypeError(`Not a valid combiner return value: ${action}`);
}
doCountDown();
}
try {
let i = 0;
for (const item of iterable) {
const index = i;
i += 1;
// Say that we have one more to wait for.
countDown += 1;
EPromise.resolve(item)
.then(
value => doCombine({ status: 'fulfilled', value }, index), // Successful resolve.
reason => doCombine({ status: 'rejected', reason }, index), // Failed resolve.
)
.catch(rejectOnce);
}
// If we had no items or they all settled before the
// loop ended, this will count down to zero and resolve
// the result.
doCountDown();
} catch (e) {
rejectOnce(e);
}
});
}
return EPromise;
}
function makeBangTransformer(parse, generate) {
let EPromise;
const transform = {
init(r, harden) {
// Create a new EPromise from the platform Promise implementation.
EPromise = harden(r.evaluate(`(${makeEPromiseClass})`)(r.global.Promise));
},
endow(es) {
if (!EPromise) {
// Not yet initialized: don't override platform Promises.
return es;
}
// Override the global Promise reference.
return {

@@ -375,0 +251,0 @@ ...es,

{
"name": "@agoric/transform-bang",
"version": "0.0.1",
"version": "0.1.0",
"description": "transform-bang",

@@ -28,3 +28,2 @@ "main": "dist/transform-bang.cjs.js",

"rollup-plugin-node-resolve": "^5.2.0",
"@agoric/babel-parser": "^7.5.0-infixBang.1",

@@ -34,3 +33,3 @@ "@babel/generator": "^7.5.0"

"dependencies": {
"@agoric/eventual-send": "0.0.1",
"@agoric/eventual-send": "^0.1.2",
"@agoric/harden": "0.0.4",

@@ -37,0 +36,0 @@ "esm": "^3.2.5",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc