-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a995a75
commit 8be558c
Showing
10 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Agoric Far Object helpers | ||
|
||
The `@agoric/far` package provides a convenient way to use the Agoric | ||
[distributed objects system](https://agoric.com/documentation/js-programming/far.html) without relying on the underlying messaging | ||
implementation. | ||
|
||
It exists to reduce the boilerplate in Hardened JavaScript vats that are running | ||
in Agoric's SwingSet kernel, | ||
[`@agoric/swingset-vat`](https://github.com/Agoric/agoric-sdk/tree/master/packages/SwingSet), | ||
or arbitrary JS programs using Hardened JavaScript and communicating via | ||
[`@agoric/captp`](https://github.com/Agoric/agoric-sdk/tree/master/packages/captp). | ||
|
||
You can import any of the following from `@agoric/far`: | ||
|
||
```js | ||
import { E, Far, getInterfaceOf, passStyleOf } from '@agoric/far'; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// This file can contain .js-specific Typescript compiler config. | ||
{ | ||
"compilerOptions": { | ||
"target": "esnext", | ||
"module": "esnext", | ||
|
||
"noEmit": true, | ||
/* | ||
// The following flags are for creating .d.ts files: | ||
"noEmit": false, | ||
"declaration": true, | ||
"emitDeclarationOnly": true, | ||
*/ | ||
"downlevelIteration": true, | ||
"strictNullChecks": true, | ||
"moduleResolution": "node", | ||
}, | ||
"include": ["src/**/*.js", "exported.js"], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"name": "@agoric/far", | ||
"version": "0.1.0", | ||
"description": "Helpers for Agoric distributed objects.", | ||
"type": "module", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "ava", | ||
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", | ||
"test:xs": "exit 0", | ||
"build": "exit 0", | ||
"lint-fix": "yarn lint:eslint --fix && yarn lint:types", | ||
"lint-check": "yarn lint", | ||
"lint": "yarn lint:types && yarn lint:eslint", | ||
"lint:types": "tsc -p jsconfig.json", | ||
"lint:eslint": "eslint '**/*.js'" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Agoric/agoric-sdk.git" | ||
}, | ||
"author": "Agoric", | ||
"license": "Apache-2.0", | ||
"bugs": { | ||
"url": "https://github.com/Agoric/agoric-sdk/issues" | ||
}, | ||
"homepage": "https://github.com/Agoric/agoric-sdk#readme", | ||
"dependencies": { | ||
"@agoric/eventual-send": "^0.13.30", | ||
"@agoric/marshal": "^0.4.28" | ||
}, | ||
"devDependencies": { | ||
"@agoric/install-ses": "^0.5.28", | ||
"@endo/ses-ava": "^0.2.8", | ||
"ava": "^3.12.1", | ||
"c8": "^7.7.2" | ||
}, | ||
"keywords": [ | ||
"eventual send", | ||
"wavy dot", | ||
"remote objects", | ||
"tildot", | ||
"far" | ||
], | ||
"files": [ | ||
"src" | ||
], | ||
"eslintConfig": { | ||
"extends": [ | ||
"@agoric" | ||
] | ||
}, | ||
"prettier": { | ||
"trailingComma": "all", | ||
"singleQuote": true | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"ava": { | ||
"files": [ | ||
"test/**/test-*.js" | ||
], | ||
"timeout": "2m" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export { E } from '@agoric/eventual-send'; | ||
export { Far, getInterfaceOf, passStyleOf } from '@agoric/marshal'; | ||
|
||
/** | ||
* @template T | ||
* @typedef {import('@agoric/eventual-send').EOnly<T>} EOnly | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import '@agoric/install-ses/debug.js'; | ||
|
||
import { wrapTest } from '@endo/ses-ava'; | ||
import rawTest from 'ava'; | ||
|
||
/** @type {typeof rawTest} */ | ||
export const test = wrapTest(rawTest); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/* global HandledPromise */ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import { test } from './prepare-test-env-ava.js'; | ||
|
||
import { E } from '../src/index.js'; | ||
|
||
test('E reexports', async t => { | ||
t.is(E.resolve, HandledPromise.resolve, 'E reexports resolve'); | ||
}); | ||
|
||
test('E.when', async t => { | ||
let stash; | ||
await E.when(123, val => (stash = val)); | ||
t.is(stash, 123, `onfulfilled handler fires`); | ||
let raised; | ||
// eslint-disable-next-line prefer-promise-reject-errors | ||
await E.when(Promise.reject('foo'), undefined, val => (raised = val)); | ||
t.assert(raised, 'foo', 'onrejected handler fires'); | ||
|
||
let ret; | ||
let exc; | ||
await E.when( | ||
Promise.resolve('foo'), | ||
val => (ret = val), | ||
val => (exc = val), | ||
); | ||
t.is(ret, 'foo', 'onfulfilled option fires'); | ||
t.is(exc, undefined, 'onrejected option does not fire'); | ||
|
||
let ret2; | ||
let exc2; | ||
await E.when( | ||
// eslint-disable-next-line prefer-promise-reject-errors | ||
Promise.reject('foo'), | ||
val => (ret2 = val), | ||
val => (exc2 = val), | ||
); | ||
t.is(ret2, undefined, 'onfulfilled option does not fire'); | ||
t.is(exc2, 'foo', 'onrejected option fires'); | ||
}); | ||
|
||
test('E method calls', async t => { | ||
const x = { | ||
double(n) { | ||
return 2 * n; | ||
}, | ||
}; | ||
const d = E(x).double(6); | ||
t.is(typeof d.then, 'function', 'return is a thenable'); | ||
t.is(await d, 12, 'method call works'); | ||
}); | ||
|
||
test('E sendOnly method calls', async t => { | ||
let testIncrDoneResolve; | ||
const testIncrDone = new Promise(resolve => { | ||
testIncrDoneResolve = resolve; | ||
}); | ||
|
||
let count = 0; | ||
const counter = { | ||
incr(n) { | ||
count += n; | ||
testIncrDoneResolve(); // only here for the test. | ||
return count; | ||
}, | ||
}; | ||
const result = E.sendOnly(counter).incr(42); | ||
t.is(typeof result, 'undefined', 'return is undefined as expected'); | ||
await testIncrDone; | ||
t.is(count, 42, 'sendOnly method call variant works'); | ||
}); | ||
|
||
test('E call missing method', async t => { | ||
const x = { | ||
double(n) { | ||
return 2 * n; | ||
}, | ||
}; | ||
await t.throwsAsync(() => E(x).triple(6), { | ||
message: 'target has no method "triple", has ["double"]', | ||
}); | ||
}); | ||
|
||
test('E sendOnly call missing method', async t => { | ||
let count = 279; | ||
const counter = { | ||
incr(n) { | ||
count += n; | ||
return count; | ||
}, | ||
}; | ||
|
||
const result = E.sendOnly(counter).decr(210); | ||
t.is(result, undefined, 'return is undefined as expected'); | ||
await null; | ||
t.is(count, 279, `sendOnly method call doesn't change count`); | ||
}); | ||
|
||
test('E call undefined method', async t => { | ||
const x = { | ||
double(n) { | ||
return 2 * n; | ||
}, | ||
}; | ||
await t.throwsAsync(() => E(x)(6), { | ||
message: 'Cannot invoke target as a function; typeof target is "object"', | ||
}); | ||
}); | ||
|
||
test('E invoke a non-method', async t => { | ||
const x = { double: 24 }; | ||
await t.throwsAsync(() => E(x).double(6), { | ||
message: 'invoked method "double" is not a function; it is a "number"', | ||
}); | ||
}); | ||
|
||
test('E method call undefined receiver', async t => { | ||
await t.throwsAsync(() => E(undefined).double(6), { | ||
message: 'Cannot deliver "double" to target; typeof target is "undefined"', | ||
}); | ||
}); | ||
|
||
test('E shortcuts', async t => { | ||
const x = { | ||
name: 'buddy', | ||
val: 123, | ||
y: Object.freeze({ | ||
val2: 456, | ||
name2: 'holly', | ||
fn: n => 2 * n, | ||
}), | ||
hello(greeting) { | ||
return `${greeting}, ${this.name}!`; | ||
}, | ||
}; | ||
t.is(await E(x).hello('Hello'), 'Hello, buddy!', 'method call works'); | ||
t.is( | ||
await E(await E.get(await E.get(x).y).fn)(4), | ||
8, | ||
'anonymous method works', | ||
); | ||
t.is(await E.get(x).val, 123, 'property get'); | ||
}); | ||
|
||
test('E.get', async t => { | ||
const x = { | ||
name: 'buddy', | ||
val: 123, | ||
y: Object.freeze({ | ||
val2: 456, | ||
name2: 'holly', | ||
fn: n => 2 * n, | ||
}), | ||
hello(greeting) { | ||
return `${greeting}, ${this.name}!`; | ||
}, | ||
}; | ||
t.is( | ||
await E(await E.get(await E.get(x).y).fn)(4), | ||
8, | ||
'anonymous method works', | ||
); | ||
t.is(await E.get(x).val, 123, 'property get'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// @ts-check | ||
|
||
import { test } from './prepare-test-env-ava.js'; | ||
|
||
import { Far, getInterfaceOf, passStyleOf } from '../src/index.js'; | ||
|
||
const { freeze, setPrototypeOf } = Object; | ||
|
||
test('Far functions', t => { | ||
t.notThrows(() => Far('arrow', a => a + 1), 'Far function'); | ||
const arrow = Far('arrow', a => a + 1); | ||
t.is(passStyleOf(arrow), 'remotable'); | ||
t.is(getInterfaceOf(arrow), 'Alleged: arrow'); | ||
}); | ||
|
||
test('Acceptable far functions', t => { | ||
t.is(passStyleOf(Far('asyncArrow', async a => a + 1)), 'remotable'); | ||
// Even though concise methods start as methods, they can be | ||
// made into far functions *instead*. | ||
const concise = { doFoo() {} }.doFoo; | ||
t.is(passStyleOf(Far('concise', concise)), 'remotable'); | ||
}); | ||
|
||
test('Unacceptable far functions', t => { | ||
t.throws( | ||
() => | ||
Far( | ||
'alreadyFrozen', | ||
freeze(a => a + 1), | ||
), | ||
{ | ||
message: /is already frozen/, | ||
}, | ||
); | ||
t.throws(() => Far('keywordFunc', function keyword() {}), { | ||
message: /unexpected properties besides \.name and \.length/, | ||
}); | ||
}); | ||
|
||
test('Far functions cannot be methods', t => { | ||
const doFoo = Far('doFoo', a => a + 1); | ||
t.throws( | ||
() => | ||
Far('badMethod', { | ||
doFoo, | ||
}), | ||
{ | ||
message: /Remotables with non-methods/, | ||
}, | ||
); | ||
}); | ||
|
||
test('Data can contain far functions', t => { | ||
const arrow = Far('arrow', a => a + 1); | ||
t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord'); | ||
const mightBeMethod = a => a + 1; | ||
t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), { | ||
message: /Remotables with non-methods like "x" /, | ||
}); | ||
}); | ||
|
||
test('function without prototype', t => { | ||
const arrow = a => a; | ||
setPrototypeOf(arrow, null); | ||
t.throws(() => Far('arrow', arrow), { | ||
message: /must not inherit from null/, | ||
}); | ||
}); |
Oops, something went wrong.