API Subscriptions (#62)

* Upgrades

* Add getHead & newHead

* Handle pubsub interfaces

* Flow fixes
This commit is contained in:
Jaco Greeff
2018-04-16 09:03:06 +02:00
committed by GitHub
parent 409db09ec1
commit 68edc80e82
16 changed files with 101 additions and 137 deletions
+1 -1
View File
@@ -34,7 +34,7 @@
},
"dependencies": {
"@polkadot/primitives-json": "^0.9.5",
"@polkadot/util": "^0.18.2",
"@polkadot/util": "^0.18.4",
"babel-runtime": "^6.26.0"
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { InterfaceMethodDefinition } from '../types';
/**
@name getHead
@signature chain_getHead (): HeaderHash
@summary Retrieves the best headerHash.
@description
Return the block hash for the lastest/best.
*/
module.exports = ({
inputs: [],
output: {
type: 'HeaderHash'
}
}: InterfaceMethodDefinition);
+5 -1
View File
@@ -5,13 +5,17 @@
import type { InterfaceDefinition } from '../types';
const getHead = require('./getHead');
const getHeader = require('./getHeader');
const newHead = require('./newHead');
/**
@summary Methods to retrieve chain data.
*/
module.exports = ({
methods: {
getHeader
getHead,
getHeader,
newHead
}
}: InterfaceDefinition);
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { InterfaceMethodDefinition } from '../types';
/**
@name getHead
@signature chain_newHead (): HeaderHash
@summary Retrieves the best headerHash via subscription.
@description
Return the block hash for the lastest/best.
*/
module.exports = ({
isSubscription: true,
inputs: [],
output: {
type: 'HeaderHash'
}
}: InterfaceMethodDefinition);
+8 -2
View File
@@ -7,7 +7,7 @@ export type InterfaceTypes = 'author' | 'chain' | 'extra' | 'state';
export type FormatInputType = 'Bytes' | 'H256' | 'HeaderHash' | 'String';
export type FormatOutputType = 'BlockNumber' | 'Bytes' | 'Header' | 'U64';
export type FormatOutputType = 'BlockNumber' | 'Bytes' | 'Header' | 'HeaderHash' | 'U64';
export type InterfaceInputType = {
name: string,
@@ -18,8 +18,14 @@ export type InterfaceOutputType = {
type: FormatOutputType
};
export type InterfaceMethodDefinition$Subscribe = {
subscribe: string,
unsubscribe: string
}
export type InterfaceMethodDefinition = {
deprecated?: boolean,
isDeprecated?: boolean,
isSubscription?: boolean,
inputs: Array<InterfaceInputType>,
output: InterfaceOutputType
};
+1 -1
View File
@@ -34,7 +34,7 @@
"nock": "^9.1.0"
},
"dependencies": {
"@polkadot/util": "^0.18.2",
"@polkadot/util": "^0.18.4",
"babel-runtime": "^6.26.0",
"isomorphic-fetch": "^2.2.1",
"websocket": "^1.0.25"
+1 -1
View File
@@ -33,7 +33,7 @@
"@polkadot/api-format": "^0.8.6",
"@polkadot/api-jsonrpc": "^0.8.6",
"@polkadot/api-provider": "^0.8.6",
"@polkadot/util": "^0.18.2",
"@polkadot/util": "^0.18.4",
"babel-runtime": "^6.26.0"
}
}
+7 -7
View File
@@ -9,23 +9,23 @@ import type { ApiInterface$Section } from '../types';
const interfaces = require('@polkadot/api-jsonrpc');
const createMethod = require('./method');
const createSubscribe = require('./subscribe');
const methodSend = require('./methodSend');
const methodSubscribe = require('./methodSubscribe');
module.exports = function createInterface (provider: ProviderInterface, section: InterfaceTypes): ApiInterface$Section {
const exposed: $Shape<ApiInterface$Section> = {};
const { methods } = interfaces[section];
exposed.subscribe = createSubscribe(provider, section, methods);
exposed.unsubscribe = provider.unsubscribe;
return Object
.keys(methods)
.reduce((exposed, name: string) => {
const rpcName = `${section}_${name}`;
const method = createMethod(provider, rpcName, methods[name]);
const def = methods[name];
exposed[name] = method;
// flowlint-next-line sketchy-null-bool:off
exposed[name] = def.isSubscription
? methodSubscribe(provider, rpcName, name, methods[name])
: methodSend(provider, rpcName, name, methods[name]);
return exposed;
}, exposed);
+6 -1
View File
@@ -16,6 +16,11 @@ jest.mock('./@polkadot/api-jsonrpc', () => ({
bleh: {
inputs: [],
output: { type: 'Address' }
},
pubsub: {
isSubscription: true,
inputs: [],
output: { type: 'Address' }
}
}
}
@@ -38,7 +43,7 @@ describe('createInterface', () => {
it('adds the specified methods to the interface', () => {
expect(Object.keys(container)).toEqual(
['subscribe', 'unsubscribe', 'blah', 'bleh']
['blah', 'bleh', 'pubsub']
);
});
@@ -5,17 +5,17 @@
import type { InterfaceMethodDefinition } from '@polkadot/api-jsonrpc/types';
import type { ProviderInterface } from '@polkadot/api-provider/types';
import type { ApiInterface$Section$Method } from '../types';
const formatOutput = require('@polkadot/api-format/output');
const ExtError = require('@polkadot/util/ext/error');
const jsonrpcSignature = require('@polkadot/util/jsonrpc/signature');
type Method = (..._params: Array<mixed>) => Promise<mixed>;
const createParams = require('./params');
module.exports = function createMethod (provider: ProviderInterface, rpcName: string, { inputs, output }: InterfaceMethodDefinition): Method {
return async (..._params: Array<mixed>): Promise<mixed> => {
module.exports = function createMethodSend (provider: ProviderInterface, rpcName: string, name: string, { inputs, output }: InterfaceMethodDefinition): ApiInterface$Section$Method {
const call = async (..._params: Array<mixed>): Promise<mixed> => {
// TODO: Deprecated warning
try {
const params = createParams(rpcName, _params, inputs);
const result = await provider.send(rpcName, params);
@@ -25,4 +25,7 @@ module.exports = function createMethod (provider: ProviderInterface, rpcName: st
throw new ExtError(`${jsonrpcSignature(rpcName, inputs, output)}:: ${error.message}`, (error: ExtError).code);
}
};
// flowlint-next-line unclear-type:off
return ((call: any): ApiInterface$Section$Method);
};
@@ -2,9 +2,9 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
const createMethod = require('./method');
const createMethod = require('./methodSend');
describe('createMethod', () => {
describe('methodCall', () => {
let methods;
let provider;
@@ -30,7 +30,7 @@ describe('createMethod', () => {
});
it('wraps errors with the call signature', () => {
const method = createMethod(provider, 'test_blah', methods.blah);
const method = createMethod(provider, 'test_blah', 'blah', methods.blah);
return method().catch((error) => {
expect(error.message).toMatch(/test_blah \(foo: Bytes\): Bytes/);
@@ -38,7 +38,7 @@ describe('createMethod', () => {
});
it('checks for mismatched parameters', () => {
const method = createMethod(provider, 'test_bleh', methods.bleh);
const method = createMethod(provider, 'test_bleh', 'bleh', methods.bleh);
return method(1).catch((error) => {
expect(error.message).toMatch(/0 params expected, found 1 instead/);
@@ -46,7 +46,7 @@ describe('createMethod', () => {
});
it('calls the provider with the correct parameters', () => {
const method = createMethod(provider, 'test_blah', methods.blah);
const method = createMethod(provider, 'test_blah', 'blah', methods.blah);
return method(new Uint8Array([0x12, 0x34])).then(() => {
expect(provider.send).toHaveBeenCalledWith('test_blah', ['0x1234']);
@@ -5,6 +5,7 @@
import type { InterfaceMethodDefinition } from '@polkadot/api-jsonrpc/types';
import type { ProviderInterface, ProviderInterface$Callback } from '@polkadot/api-provider/types';
import type { ApiInterface$Section$Method } from '../types';
const formatOutput = require('@polkadot/api-format/output');
const assert = require('@polkadot/util/assert');
@@ -12,12 +13,12 @@ const ExtError = require('@polkadot/util/ext/error');
const isFunction = require('@polkadot/util/is/function');
const jsonrpcSignature = require('@polkadot/util/jsonrpc/signature');
type Method = (...params: Array<mixed>) => Promise<number>;
const createParams = require('./params');
module.exports = function createSubscribeMethod (provider: ProviderInterface, rpcName: string, { inputs, output }: InterfaceMethodDefinition): Method {
return async (..._params: Array<mixed>): Promise<number> => {
module.exports = function methodSubscribe (provider: ProviderInterface, rpcName: string, name: string, { inputs, output }: InterfaceMethodDefinition): ApiInterface$Section$Method {
const unsubscribe = (subscriptionId: mixed): Promise<mixed> =>
provider.send(`unsubscribe_${name}`, [subscriptionId]);
const call = async (..._params: Array<mixed>): Promise<mixed> => {
try {
// flowlint-next-line unclear-type:off
const cb = ((_params.pop(): any): ProviderInterface$Callback);
@@ -26,7 +27,7 @@ module.exports = function createSubscribeMethod (provider: ProviderInterface, rp
const params = createParams(rpcName, _params, inputs);
return provider.subscribe(rpcName, params, (error: ?Error, result: mixed) => {
return provider.subscribe(`subscribe_${name}`, params, (error: ?Error, result: mixed) => {
if (error) {
cb(error);
} else {
@@ -37,4 +38,9 @@ module.exports = function createSubscribeMethod (provider: ProviderInterface, rp
throw new ExtError(`${jsonrpcSignature(rpcName, inputs, output)}:: ${error.message}`, (error: ExtError).code);
}
};
call.unsubscribe = unsubscribe;
// flowlint-next-line unclear-type:off
return ((call: any): ApiInterface$Section$Method);
};
-25
View File
@@ -1,25 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { InterfaceDefinition$Methods } from '@polkadot/api-jsonrpc/types';
import type { ProviderInterface } from '@polkadot/api-provider/types';
const assert = require('@polkadot/util/assert');
type Method = (name: string, ...params: Array<mixed>) => Promise<number>;
const subscribeMethod = require('./subscribeMethod');
module.exports = function createSubscribe (provider: ProviderInterface, section: string, methods: InterfaceDefinition$Methods): Method {
return async (name: string, ...params: Array<mixed>): Promise<number> => {
const rpcName = `${section}_${name}`;
assert(methods[name], `Unable to find '${rpcName}' subscription`);
const fn = subscribeMethod(provider, rpcName, methods[name]);
return fn.apply(null, params);
};
};
-76
View File
@@ -1,76 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
const createSubscribe = require('./subscribe');
describe('createSubscribe', () => {
let methods;
let provider;
let sub;
let cb;
beforeEach(() => {
methods = {
blah: {
inputs: [
{ name: 'foo', type: 'Address' }
],
output: { type: 'Address' }
},
bleh: {
inputs: [],
output: { type: 'Address' }
}
};
provider = {
subscribe: jest.fn((method, params, _cb) => {
cb = _cb;
return Promise.resolve(12345);
}),
unsubscribe: jest.fn(() => {
return Promise.resolve(true);
})
};
sub = createSubscribe(provider, 'test', methods);
});
it('it does not subscribe to not-found endpoint', () => {
return sub('notFound').catch((error) => {
expect(error.message).toMatch(/Unable to find 'test_notFound' subscription/);
});
});
it('returns the subscription', () => {
return sub('bleh', () => true).then((id) => {
expect(id).toEqual(12345);
});
});
it('returns values as they are available', (done) => {
sub('bleh', () => {
done();
});
cb(null, '0x123');
});
it('returns errors as they are available', (done) => {
sub('bleh', (error) => {
expect(error.message).toEqual('test error');
done();
});
cb(new Error('test error'));
});
it('checks that valid callback is provided', () => {
return sub('bleh').catch((error) => {
expect(error.message).toMatch(/Expected callback in last position/);
});
});
});
+5 -5
View File
@@ -5,13 +5,13 @@
import type { InterfaceTypes } from '@polkadot/api-jsonrpc/types';
export type ApiInterface$Section$Method = (...params: Array<mixed>) => Promise<mixed>;
export type ApiInterface$Section$Method = {
(...params: Array<mixed>): Promise<mixed>;
unsubscribe: (id: number) => Promise<mixed>;
}
export type ApiInterface$Section = {
[string]: ApiInterface$Section$Method,
subscribe: (name: string, ...params: Array<mixed>) => Promise<number>,
unsubscribe: (id: number) => Promise<boolean>
[string]: ApiInterface$Section$Method
};
export type ApiInterface = {
+3 -3
View File
@@ -209,9 +209,9 @@
deasync "^0.1.12"
ip-regex "^2.1.0"
"@polkadot/util@^0.18.2":
version "0.18.2"
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.18.2.tgz#a00fc2c27e63f9ed1b297eb9109db97234677194"
"@polkadot/util@^0.18.4":
version "0.18.4"
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.18.4.tgz#b1886a3d4663d302fe1234705f506fae95b371b8"
dependencies:
babel-runtime "^6.26.0"
bn.js "^4.11.8"