9 Commits
Author SHA1 Message Date
Kenton Varda 65edaa8a57 Promise capabilities take ownership of the capability to which they resolve.
Make sure that the node-level capability is replaced with one that will throw an exception from calls, rather than segfault on null pointer.
2016-06-20 14:28:00 -07:00
Kenton Varda 27ef02fac9 Support "flat" encoding, and "packed-flat" too. 2016-03-27 07:16:58 -07:00
Kenton Varda 39743ab28a Include Javascript stack when converting to KJ exception. 2016-03-27 07:16:58 -07:00
Kenton Varda 1c39e04adf Merge pull request #27 from zarvox/parse-packed
Add support for parsing structures in Cap'n Proto's packed encoding
2016-02-18 20:00:23 -08:00
Drew Fisher dcf1b10278 Make the overhead computation look less like it has an integer underflow 2016-02-18 19:00:57 -08:00
Drew Fisher ccdb72c931 Compute a tighter upper bound on the buffer needed for packed encoding 2016-02-18 15:48:41 -08:00
Drew Fisher 09c79de07d Add tests for packed encoding/decoding. 2016-02-18 15:11:13 -08:00
Drew Fisher b013752ed5 Add support for serializing structures to packed Cap'n Proto encoding 2016-02-18 14:00:10 -08:00
Drew Fisher 6b899a4250 Add support for parsing structures in Cap'n Proto's packed encoding 2016-02-18 12:03:03 -08:00
17 changed files with 547 additions and 1121 deletions

No files matched your search

+3 -38
View File
@@ -22,7 +22,7 @@ messages from complete Javascript objects. This actually makes the library
somewhat nicer syntactically than it would be otherwise, but it is not fast.
A pure-Javascript implementation would likely be much faster. See
[capnp-js](https://github.com/capnp-js) for such an
[capnp-js](https://github.com/capnp-js/serialization) for such an
implementation. Unfortunately, that implementation is incomplete and does not
support [RPC](https://github.com/capnp-js/rpc) just yet. Hence, this hack was
created for short-term use.
@@ -49,9 +49,9 @@ On Debian/Ubuntu, you can install these like so:
sudo apt-get install nodejs-dev nodejs-legacy capnproto-dev g++
### From source
### Form source
git clone git://github.com/capnproto/node-capnp.git
git clone git://github.com/kenton/node-capnp.git
cd node-capnp
npm install
@@ -118,25 +118,6 @@ we copied.)
// object containing the results by name.
promise.then(function(response) {
console.log(response.namedResult);
})
// If the remote object throws an rpc exception, the promise will be
// rejected. The error returned will have a property `kjType`, which
// will be a string representation of the exception's `type` field.
.catch(function(error) {
switch(error.kjType) {
case 'failed':
console.log("A generic problem occurred:", error.message);
break;
case 'overloaded':
console.log("Resource overload on the remote end:", error.message);
break;
case 'disconnected':
// ...
break;
case 'unimplemented':
// ...
break;
}
});
### Pipelining
@@ -215,22 +196,6 @@ then use that:
In this case, the library only wraps `myFoo` as a capability once, and then
calls `close()` once all copies of that reference have been dropped.
If one of your methods throws an exception, or returns a promise which
is subsequently rejected, it will be converted to a capnproto rpc
exception. If the exception has a `kjType` property, that will be used
for the exception's `type` field, otherwise the type will be `failed`.
var myBar = {
bar: function(foo) {
if(!bazAvailable()) {
var err = new Error("The baz is busy; try again later.");
err.kjType = 'overloaded';
throw(err);
}
// ...
}
}
### Exporting a bootstrap capability
// Connect to another server and export `myFoo` as a bootstrap capability.
-1
View File
@@ -1 +0,0 @@
capnp.node
+2 -2
View File
@@ -4,12 +4,12 @@
'target_name': 'capnp',
'sources': ['src/node-capnp/capnp.cc'],
'libraries': ['-lkj', '-lkj-async', '-lcapnp', '-lcapnpc', '-lcapnp-rpc'],
'cflags_cc': ['-std=c++14'],
'cflags_cc': ['-std=c++11'],
'cflags_cc!': ['-fno-rtti', '-fno-exceptions'],
'conditions': [
[ 'OS=="mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS' : ['-std=c++14','-stdlib=libc++'],
'OTHER_CPLUSPLUSFLAGS' : ['-std=c++11','-stdlib=libc++'],
'OTHER_LDFLAGS': ['-stdlib=libc++'],
'GCC_ENABLE_CPP_RTTI': 'YES',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
+3 -7
View File
@@ -72,13 +72,10 @@ if (!force) {
// Build it
function build() {
var sp = cp.spawn(
cp.spawn(
process.platform === 'win32' ? 'node-gyp.cmd' : 'node-gyp',
['rebuild'].concat(args),
{customFds: [0, 1, 2]});
sp
.on('close', function(){ afterBuild(); })
{customFds: [0, 1, 2]})
.on('exit', function(err) {
if (err) {
if (err === 127) {
@@ -91,9 +88,8 @@ function build() {
}
return process.exit(err);
}
afterBuild();
});
sp.stdout.pipe(process.stdout);
sp.stderr.pipe(process.stderr);
}
// Move it to expected location
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/c++.capnp");
+7 -7
View File
@@ -1,11 +1,8 @@
{
"name": "capnp",
"version": "0.5.0",
"version": "0.1.11",
"description": "A wrapper for the C++ Cap'n Proto library",
"keywords": [
"capnproto",
"serialization"
],
"keywords": [ "capnproto", "serialization" ],
"homepage": "http://capnproto.org",
"author": {
"name": "Kenton Varda",
@@ -18,8 +15,11 @@
"email": "jparyani@gmail.com"
}
],
"main": "src/node-capnp/capnp.js",
"types": "src/node-capnp/capnp.d.ts",
"peerDependencies": {
"es6-promise": "1.x"
},
"scripts": {
"install": "node ./build.js",
"test": "node src/node-capnp/capnp-test.js"
@@ -29,7 +29,7 @@
"url": "git://github.com/kentonv/node-capnp.git"
},
"engines": {
"node": ">=10.0"
"node": "0.10.x"
},
"license": "MIT"
}
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/persistent.capnp");
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/rpc-twoparty.capnp");
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/rpc.capnp");
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/schema.capnp");
+28 -150
View File
@@ -22,6 +22,7 @@
var fs = require("fs");
var capnp = require("./capnp");
var assert = require("assert");
var Promise = require("es6-promise").Promise;
var spawn = require("child_process").spawn;
var goldenBinary;
@@ -87,117 +88,11 @@ for (var key in keys) {
assert.equal(parsed[key], parsedPackedFlat[key]);
}
console.log("serialization: pass");
// =======================================================================================
// Test matchPowerboxQuery
if (capnp.matchPowerboxQuery) {
var tag1 = capnp.serialize(test.TestAllTypes, {int32Field: 123});
var tag2 = capnp.serialize(test.TestAllTypes, {int32Field: 321});
var tag3 = capnp.serialize(test.TestAllTypes, {});
assert(capnp.matchPowerboxQuery(tag1, tag1));
assert(capnp.matchPowerboxQuery(tag2, tag2));
assert(capnp.matchPowerboxQuery(tag3, tag3));
assert(!capnp.matchPowerboxQuery(tag1, tag2));
assert(!capnp.matchPowerboxQuery(tag2, tag3));
assert(!capnp.matchPowerboxQuery(tag3, tag1));
var emptyTag = capnp.serialize(test.TestEmptyStruct, {});
assert(!capnp.matchPowerboxQuery(emptyTag, tag1));
assert(!capnp.matchPowerboxQuery(emptyTag, tag2));
assert(capnp.matchPowerboxQuery(emptyTag, tag3));
assert(!capnp.matchPowerboxQuery(tag1, emptyTag));
assert(!capnp.matchPowerboxQuery(tag2, emptyTag));
assert(capnp.matchPowerboxQuery(tag3, emptyTag));
// Test comparing pointers.
var tagStr1 = capnp.serialize(test.TestAllTypes, {textField: "foo"});
var tagStr2 = capnp.serialize(test.TestAllTypes, {textField: "bar"});
var tagStr3 = capnp.serialize(test.TestAllTypes, {textField: ""});
assert(capnp.matchPowerboxQuery(tagStr1, tagStr1));
assert(capnp.matchPowerboxQuery(tagStr2, tagStr2));
assert(capnp.matchPowerboxQuery(tagStr3, tagStr3));
assert(!capnp.matchPowerboxQuery(tagStr1, tagStr2));
assert(!capnp.matchPowerboxQuery(tagStr2, tagStr3));
assert(!capnp.matchPowerboxQuery(tagStr3, tagStr1));
assert(capnp.matchPowerboxQuery(tagStr1, emptyTag));
assert(capnp.matchPowerboxQuery(tagStr2, emptyTag));
assert(capnp.matchPowerboxQuery(tagStr3, emptyTag));
assert(capnp.matchPowerboxQuery(emptyTag, tagStr1));
assert(capnp.matchPowerboxQuery(emptyTag, tagStr2));
assert(capnp.matchPowerboxQuery(emptyTag, tagStr3));
// Note that string comparisons are actually list comparisons. But let's make sure an exact match
// is required (which is not the case for list-of-structs).
var tagStr4 = capnp.serialize(test.TestAllTypes, {textField: "oof"});
assert(!capnp.matchPowerboxQuery(tagStr1, tagStr4));
// Test list-of-structs.
var tagFooBar = capnp.serialize(test.TestAllTypes,
{structList: [{textField: "foo"}, {textField: "bar"}]});
var tagBarFoo = capnp.serialize(test.TestAllTypes,
{structList: [{textField: "bar"}, {textField: "foo"}]});
var tagFooOnly = capnp.serialize(test.TestAllTypes,
{structList: [{textField: "foo"}]});
var tagBarOnly = capnp.serialize(test.TestAllTypes,
{structList: [{textField: "bar"}]});
var tagEmptyList = capnp.serialize(test.TestAllTypes,
{structList: []});
assert(capnp.matchPowerboxQuery(tagFooBar, tagFooBar));
assert(capnp.matchPowerboxQuery(tagFooBar, tagBarFoo));
assert(capnp.matchPowerboxQuery(tagBarFoo, tagFooBar));
assert(capnp.matchPowerboxQuery(tagFooOnly, tagFooBar));
assert(capnp.matchPowerboxQuery(tagBarOnly, tagFooBar));
assert(capnp.matchPowerboxQuery(tagFooOnly, tagBarFoo));
assert(capnp.matchPowerboxQuery(tagBarOnly, tagBarFoo));
assert(!capnp.matchPowerboxQuery(tagFooBar, tagFooOnly));
assert(!capnp.matchPowerboxQuery(tagFooBar, tagBarOnly));
assert(!capnp.matchPowerboxQuery(tagBarFoo, tagFooOnly));
assert(!capnp.matchPowerboxQuery(tagBarFoo, tagBarOnly));
assert(capnp.matchPowerboxQuery(tagFooBar, emptyTag));
assert(capnp.matchPowerboxQuery(tagFooOnly, emptyTag));
assert(capnp.matchPowerboxQuery(tagBarOnly, emptyTag));
assert(capnp.matchPowerboxQuery(emptyTag, tagFooBar));
assert(capnp.matchPowerboxQuery(emptyTag, tagFooOnly));
assert(capnp.matchPowerboxQuery(emptyTag, tagBarOnly));
assert(!capnp.matchPowerboxQuery(tagFooBar, tagEmptyList));
assert(!capnp.matchPowerboxQuery(tagFooOnly, tagEmptyList));
assert(!capnp.matchPowerboxQuery(tagBarOnly, tagEmptyList));
assert(capnp.matchPowerboxQuery(tagEmptyList, tagFooBar));
assert(capnp.matchPowerboxQuery(tagEmptyList, tagFooOnly));
assert(capnp.matchPowerboxQuery(tagEmptyList, tagBarOnly));
// Test list-of-pointers.
var tagStrList1 = capnp.serialize(test.TestAllTypes, {textList: ["foo", "bar"]});
var tagStrList2 = capnp.serialize(test.TestAllTypes, {textList: ["bar", "foo"]});
var tagStrList3 = capnp.serialize(test.TestAllTypes, {textList: ["foo"]});
var tagStrList4 = capnp.serialize(test.TestAllTypes, {textList: ["foo", null]});
assert(capnp.matchPowerboxQuery(tagStrList1, tagStrList1));
assert(capnp.matchPowerboxQuery(tagStrList2, tagStrList2));
assert(!capnp.matchPowerboxQuery(tagStrList1, tagStrList2));
assert(!capnp.matchPowerboxQuery(tagStrList2, tagStrList1));
assert(!capnp.matchPowerboxQuery(tagStrList3, tagStrList1));
assert(capnp.matchPowerboxQuery(tagStrList4, tagStrList1));
console.log("matchPowerboxQuery: pass");
}
// =======================================================================================
// Test RPC, if possible.
if (!fs.existsSync("capnp-samples")) {
console.warn("skipping RPC because capnp-samples not present");
process.exit(0);
}
/*
// TODO(someday): Revive this test. The main problem is that it depends on calculator-server from
// the Cap'n Proto samples directory.
var Fiber = require("fibers");
@@ -249,7 +144,7 @@ child.stdio[1].once("readable", function() {
doFiber(function() {
var conn = capnp.connect("127.0.0.1:21311");
var Calculator = capnp.import("capnp-samples/calculator.capnp").Calculator;
var calc = conn.restore(null, Calculator);
var calc = conn.restore("calculator", Calculator);
var add = calc.getOperator("add").func;
var subtract = calc.getOperator("subtract").func;
@@ -263,48 +158,28 @@ child.stdio[1].once("readable", function() {
closed: false
};
function usePow(pow) {
var localCap = new capnp.Capability(pow, Calculator.Function);
assert.equal(9, wait(localCap.call([3, 2])).value);
assert(!pow.closed);
localCap.close();
assert(pow.closed);
var localCap = new capnp.Capability(pow, Calculator.Function);
assert.equal(9, wait(localCap.call([3, 2])).value);
assert(!pow.closed);
localCap.close();
assert(pow.closed);
var promise = calc.evaluate(
{call: {"function": subtract, params: [
{call: {"function": add, params: [
{literal: 123}, {literal: 456}]}},
{literal: 321}]}});
var promise = calc.evaluate(
{call: {"function": subtract, params: [
{call: {"function": add, params: [
{literal: 123}, {literal: 456}]}},
{literal: 321}]}});
var value = promise.value;
assert.equal(258, wait(value.read()).value);
value.close();
var value = promise.value;
assert.equal(258, wait(value.read()).value);
value.close();
pow.closed = false;
value = calc.evaluate(
{call: {"function": pow, params: [{literal: 2}, {literal: 4}]}}).value;
assert.equal(16, wait(value.read()).value);
// Wait a moment to give the capability a chance to be dropped.
wait(new Promise((resolve, reject) => setTimeout(resolve, 10)));
assert(pow.closed);
value.close();
}
usePow(pow);
// Try using a class.
class PowClass {
constructor() {
this.closed = false
}
call(params) {
return Math.pow(params[0], params[1]);
}
close() {
this.closed = true;
}
};
usePow(new PowClass());
pow.closed = false;
value = calc.evaluate(
{call: {"function": pow, params: [{literal: 2}, {literal: 4}]}}).value;
assert.equal(16, wait(value.read()).value);
assert(pow.closed); // Not kept past return.
value.close();
// Try wrapping a promise as a capability -- calls are queued until resolution.
var resolvePromisedCalc;
@@ -336,9 +211,12 @@ child.stdio[1].once("readable", function() {
add.close();
subtract.close();
calc.close();
conn.close();
console.log("rpc: pass");
}, child);
});
*/
console.log("pass");
+455 -833
View File
File diff suppressed because it is too large. Load diff
-45
View File
@@ -1,45 +0,0 @@
declare module Capnp {
type Id = string;
abstract class StructSchema<Builder, Reader> {
// A schema for a struct type. The `Builder` parameter is what users of
// the library specify in order to create a value; it may be missing fields,
// which will be filled in with defaults. A `Reader` is what is is returned
// when parsing a value, and is guaranteed to have all known fields set.
typeId: Id;
}
abstract class InterfaceSchema<Server, Client> {
// A schema for an interface type. Similarly to `StructSchema`, the `Server`
// and `Client` parameters represent objects which may have missing
// methods (for servers), and ones which do not (for clients, though calling
// the method may still throw unimplemented).
typeId: Id;
}
interface AnyServer {
close?: () => void;
}
interface AnyClient {
close(): void;
closed: boolean;
castAs<Server, Client>(schema: InterfaceSchema<Server, Client>): Client;
}
const Capability: {
new<Server, Client>(server: Server, schema: InterfaceSchema<Server, Client>): Client;
}
abstract class Connection {
restore<Server, Client>(exportName: string, type: InterfaceSchema<Server, Client>): Client;
close(): void;
}
function parse<Builder, Reader>(type: StructSchema<Builder, Reader>, buffer: Buffer): Reader;
function serialize<Builder, Reader>(type: StructSchema<Builder, Reader>, builder: Builder): Buffer;
function connect(addr: string): Connection;
}
export default Capnp;
+16 -32
View File
@@ -40,11 +40,12 @@ try {
} catch (ex) {
// Give up.
throw new Error(
"`capnp.node` is missing. Try reinstalling `node-capnp`?");
"`" + modPath+ ".node` is missing. Try reinstalling `node-capnp`?");
}
}
var v8capnp = require(modPath);
var Promise = require("es6-promise").Promise;
var importPath = [];
for (var i in module.paths) {
@@ -99,7 +100,7 @@ function closeAll(obj) {
var member = obj[name];
if (member instanceof Capability) {
member.close();
} else if (typeof member === "object") {
} else {
closeAll(member);
}
}
@@ -166,13 +167,9 @@ function makeMethod(cap, method) {
function wrapLocalMethod(self, method) {
return function (request) {
// We start with Promise.resolve().then(...) so that any exceptions thrown immediately
// propagate through the promise chain.
Promise.resolve().then(function () {
var params = v8capnp.toJsParams(request, Capability);
v8capnp.releaseParams(request);
return method.apply(self, params);
}).then(function (results) {
var params = v8capnp.toJsParams(request, Capability);
v8capnp.releaseParams(request);
Promise.resolve(method.apply(self, params)).then(function (results) {
if (typeof results !== "object") {
if (results === undefined) {
results = [];
@@ -192,20 +189,16 @@ function wrapLocalMethod(self, method) {
}
function LocalCapWrapper(obj) {
var type = obj;
while (type && type !== Object.prototype) {
Object.getOwnPropertyNames(type).forEach(name => {
if (name === "close") {
// Not an RPC method. Called when the capability has no clients.
this.close = obj.close.bind(obj);
} else {
var method = obj[name];
if (typeof method === "function") {
this[name] = wrapLocalMethod(obj, method);
}
for (var name in obj) {
if (name === "close") {
// Not an RPC method. Called when the capability has no clients.
this.close = obj.close.bind(obj);
} else {
var method = obj[name];
if (typeof method === "function") {
this[name] = wrapLocalMethod(obj, method);
}
})
type = Object.getPrototypeOf(type);
}
}
}
@@ -260,16 +253,9 @@ exports.connect = function (addr, bootstrapCap) {
if (bootstrapCap && !v8capnp.isCap(bootstrapCap)) {
throw new Error("Invalid bootstrap capability.");
}
if (addr.capabilityStreamFd) {
return new Connection(v8capnp.connectUnixFd(addr.capabilityStreamFd, bootstrapCap));
} else {
return new Connection(v8capnp.connect(addr, bootstrapCap));
}
return new Connection(v8capnp.connect(addr, bootstrapCap));
}
exports.expectedSizeFromPrefix = v8capnp.expectedSizeFromPrefix;
exports.parse = function (schema, buffer, options) {
var reader = v8capnp.fromBytes(buffer, schema, options || {});
return v8capnp.toJs(reader, Capability);
@@ -300,5 +286,3 @@ exports.bytesToPreorder = function(schema, buf, options) {
return v8capnp.toBytes(builder, options || {});
}
if (v8capnp.matchPowerboxQuery) exports.matchPowerboxQuery = v8capnp.matchPowerboxQuery;
if (v8capnp.chacha20) exports.chacha20 = v8capnp.chacha20;
+32
View File
@@ -0,0 +1,32 @@
#! /bin/sh
# Copyright (c) 2014 Sandstorm Development Group, Inc. and contributors
# Licensed under the MIT License:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
set -eu
echo newOutput node-capnp/es6-promise.js
read OUTPUT
curl -s https://es6-promises.s3.amazonaws.com/es6-promise-2.0.1.min.js > "$OUTPUT"
echo "4f423e916c1ced14fc56c83478bd1ff17000f32ea399fda24fd131989265719c $OUTPUT" | sha256sum --quiet --strict -c - >&2
echo $OUTPUT >&2
+1
View File
@@ -1,2 +1,3 @@
capnp.node node_modules
capnp.js node_modules
es6-promise.js node_modules
-1
View File
@@ -1 +0,0 @@
module.exports = require("capnp").importSystem("capnp/stream.capnp");