Remove create/* folder for API, include priv methods (#156)

* Remove create/* folder for API, include priv methods

* Move create/* into Api-rx as private

* Modularize subsequent value caching
This commit is contained in:
Jaco Greeff
2018-09-17 10:33:57 +02:00
committed by GitHub
parent 5a513ac09e
commit 5f8c52d1e1
20 changed files with 267 additions and 521 deletions
@@ -2,12 +2,23 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import cachedSubscription from './cached';
jest.mock('@polkadot/api-provider/ws', () => class {
isConnected = () => true;
on = () => true;
send = () => true;
});
describe('cached', () => {
const Api = require('./index').default;
describe('createCachedObservable', () => {
let api;
let creator;
let section;
beforeEach(() => {
api = new Api();
});
beforeEach(() => {
const subMethod = jest.fn((name, ...params) => {
return Promise.resolve(12345);
@@ -26,7 +37,7 @@ describe('cached', () => {
subMethod
};
creator = cachedSubscription('test', 'subMethod', section);
creator = api.createCachedObservable('test', 'subMethod', section);
});
it('creates a single observable', () => {
-1
View File
@@ -9,7 +9,6 @@ jest.mock('@polkadot/api-provider/ws', () => class {
on = () => true;
send = () => true;
});
jest.mock('./interface', () => (api, sectionName) => sectionName);
const Api = require('./index').default;
+122 -7
View File
@@ -2,16 +2,24 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ApiInterface } from '@polkadot/api/types';
import { ApiInterface, ApiInterface$Section } from '@polkadot/api/types';
import { ProviderInterface } from '@polkadot/api-provider/types';
import { Interface$Sections } from '@polkadot/jsonrpc/types';
import { RxApiInterface, RxApiInterface$Section } from './types';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, Observable, Subscriber, from } from 'rxjs';
import Api from '@polkadot/api/index';
import Ws from '@polkadot/api-provider/ws';
import isFunction from '@polkadot/util/is/function';
import isUndefined from '@polkadot/util/is/undefined';
import defaults from './defaults';
import createInterface from './interface';
type CachedMap = {
[index: string]: {
[index: string]: BehaviorSubject<any>
}
};
/**
* An RxJs wrapper around the [[api]].
@@ -20,12 +28,14 @@ import createInterface from './interface';
*
* import RxApi from '@polkadot/api';
* import WsProvider from '@polkadot/api-provider/ws';
*
* const provider = new WsProvider('http://127.0.0.1:9944');
* const rxapi = new RxApi(provider);
* ```
*/
export default class RxApi implements RxApiInterface {
private _api: ApiInterface;
private _cacheMap: CachedMap;
private _isConnected: BehaviorSubject<boolean>;
readonly author: RxApiInterface$Section;
readonly chain: RxApiInterface$Section;
@@ -37,18 +47,123 @@ export default class RxApi implements RxApiInterface {
*/
constructor (provider: ProviderInterface = new Ws(defaults.WS_URL)) {
this._api = new Api(provider);
this._cacheMap = {};
this._isConnected = new BehaviorSubject(provider.isConnected());
provider.on('connected', () => this._isConnected.next(true));
provider.on('disconnected', () => this._isConnected.next(false));
this.author = createInterface(this._api, 'author');
this.chain = createInterface(this._api, 'chain');
this.state = createInterface(this._api, 'state');
this.system = createInterface(this._api, 'system');
this.author = this.createInterface('author');
this.chain = this.createInterface('chain');
this.state = this.createInterface('state');
this.system = this.createInterface('system');
}
isConnected (): BehaviorSubject<boolean> {
return this._isConnected;
}
private createInterface (sectionName: Interface$Sections): RxApiInterface$Section {
const section: ApiInterface$Section = this._api[sectionName];
return Object
.keys(section)
.filter((name) => !['subscribe', 'unsubscribe'].includes(name))
.reduce((observables, name) => {
observables[name] = this.createObservable(`${sectionName}_${name}`, name, section);
return observables;
}, ({} as RxApiInterface$Section));
}
private createObservable (subName: string, name: string, section: ApiInterface$Section): (...params: Array<any>) => Observable<any> | BehaviorSubject<any> {
if (isFunction(section[name].unsubscribe)) {
return this.createCachedObservable(subName, name, section);
}
return (...params: Array<any>): Observable<any> =>
from(
section[name]
.apply(null, params)
.catch((error: Error) => {
console.error(error);
})
);
}
private createCachedObservable (subName: string, name: string, section: ApiInterface$Section): (...params: Array<any>) => BehaviorSubject<any> {
if (!this._cacheMap[subName]) {
this._cacheMap[subName] = {};
}
return (...params: Array<any>): BehaviorSubject<any> => {
const paramStr = JSON.stringify(params);
if (!this._cacheMap[subName][paramStr]) {
this._cacheMap[subName][paramStr] = this.createSubject(name, params, section);
}
return this._cacheMap[subName][paramStr];
};
}
private createSubject (name: string, params: Array<any>, section: ApiInterface$Section, unsubCallback?: () => void): BehaviorSubject<any> {
const subject = new BehaviorSubject(undefined);
Observable
.create((observer: Subscriber<any>): Function => {
try {
const fn = section[name];
const subscribe = fn(...params, this.createSubjectCallback(observer));
return (): void => {
subscribe
.then((subscriptionId: number) =>
fn.unsubscribe(subscriptionId)
)
.then(() =>
isFunction(unsubCallback) && unsubCallback()
)
.catch((error) => {
console.error('Unsubscribe failed', error);
});
};
} catch (error) {
console.error(error);
return (): void => {
console.error('Unsubscribe called on previously failed subscription', error);
};
}
})
.subscribe(subject);
return subject;
}
private createSubjectCallback (observer: Subscriber<any>) {
let cachedResult: any;
return (error: Error | null, result: any) => {
if (error) {
console.error(error);
observer.next();
return;
}
if (isUndefined(cachedResult) || !Array.isArray(cachedResult)) {
cachedResult = result;
} else {
const resultArray = (result as Array<any>) || [];
cachedResult = cachedResult.map((cachedValue, index) =>
isUndefined(resultArray[index])
? cachedValue
: resultArray[index]
);
}
observer.next(cachedResult);
};
}
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
jest.mock('./observable', () => (subName, name, section) => `${subName}_${name}_${Object.keys(section).join(':')}`);
const createInterface = require('./interface').default;
describe('createInterface', () => {
let api;
let int;
beforeEach(() => {
api = {
chain: {
foo: 'test',
bar: 'test',
subscribe: 'noInclude',
unsubscribe: 'noInclude'
},
state: {
baz: 'test'
}
};
int = createInterface(api, 'chain');
});
it('creates observables for all relevant methods', () => {
expect(
int
).toEqual({
foo: 'chain_foo_foo_foo:bar:subscribe:unsubscribe',
bar: 'chain_bar_bar_foo:bar:subscribe:unsubscribe'
});
});
});
-22
View File
@@ -1,22 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ApiInterface, ApiInterface$Section } from '@polkadot/api/types';
import { Interface$Sections } from '@polkadot/jsonrpc/types';
import { RxApiInterface$Section } from './types';
import observable from './observable';
export default function createInterface (api: ApiInterface, sectionName: Interface$Sections): RxApiInterface$Section {
const section: ApiInterface$Section = api[sectionName];
return Object
.keys(section)
.filter((name) => !['subscribe', 'unsubscribe'].includes(name))
.reduce((observables, name) => {
observables[name] = observable(`${sectionName}_${name}`, name, section);
return observables;
}, ({} as RxApiInterface$Section));
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ApiInterface$Section } from '@polkadot/api/types';
import { BehaviorSubject } from 'rxjs';
type CachedMap = {
[index: string]: {
[index: string]: BehaviorSubject<any>
}
};
import subject from './subject';
const cacheMap: CachedMap = {};
export default function cached (subName: string, name: string, section: ApiInterface$Section): (...params: Array<any>) => BehaviorSubject<any> {
if (!cacheMap[subName]) {
cacheMap[subName] = {};
}
return (...params: Array<any>): BehaviorSubject<any> => {
const paramStr = JSON.stringify(params);
if (!cacheMap[subName][paramStr]) {
cacheMap[subName][paramStr] = subject(name, params, section);
}
return cacheMap[subName][paramStr];
};
}
@@ -1,38 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import createObservable from './index';
describe('observable', () => {
let section;
beforeEach(() => {
const withSub = () => {};
withSub.unsubscribe = jest.fn();
section = {
'test_noSub': jest.fn(() => Promise.resolve(true)),
'test_withSub': withSub,
subscribe: jest.fn((name, params, _callback) => {
return Promise.resolve(12345);
}),
unsubscribe: jest.fn(() => {
return Promise.resolve(true);
})
};
});
it('creates a single-shot observable', () => {
expect(
createObservable('test_noSub', 'test_noSub', section)()
).toBeDefined();
});
it('creates a subscription observable', () => {
expect(
createObservable('test_withSub', 'test_withSub', section)()
).toBeDefined();
});
});
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ApiInterface$Section } from '@polkadot/api/types';
import { BehaviorSubject, Observable, from } from 'rxjs';
import isFunction from '@polkadot/util/is/function';
// import isNull from '@polkadot/util/is/null';
// import isUndefined from '@polkadot/util/is/undefined';
import cached from './cached';
export default function observable (subName: string, name: string, section: ApiInterface$Section): (...params: Array<any>) => Observable<any> | BehaviorSubject<any> {
if (isFunction(section[name].unsubscribe)) {
return cached(subName, name, section);
}
return (...params: Array<any>): Observable<any> =>
from(
section[name]
.apply(null, params)
.catch((error: Error) => {
console.error(error);
})
);
}
-68
View File
@@ -1,68 +0,0 @@
// Copyright 2017-2018 @polkadot/api-rx authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ApiInterface$Section } from '@polkadot/api/types';
import { BehaviorSubject, Observable, Subscriber } from 'rxjs';
import isUndefined from '@polkadot/util/is/undefined';
export default function subscription (name: string, params: Array<any>, section: ApiInterface$Section, unsubCallback?: () => void): BehaviorSubject<any> {
const subject = new BehaviorSubject(undefined);
Observable
.create((observer: Subscriber<any>): Function => {
let cachedResult: any;
const callback = (error: Error | null, result: any) => {
if (error) {
console.error(error);
observer.next();
return;
}
if (isUndefined(cachedResult) || !Array.isArray(cachedResult)) {
cachedResult = result;
} else {
const resultArray = (result as Array<any>) || [];
cachedResult = cachedResult.map((cachedValue, index) =>
isUndefined(resultArray[index])
? cachedValue
: resultArray[index]
);
}
observer.next(cachedResult);
};
try {
const fn = section[name];
const subscribe = fn(...params, callback);
return (): void => {
subscribe
.then((subscriptionId: number) =>
fn.unsubscribe(subscriptionId)
)
.then(() => {
if (unsubCallback) {
unsubCallback();
}
})
.catch((error) => {
console.error('Unsubscribe failed', error);
});
};
} catch (error) {
console.error(error);
return (): void => {
console.error('Unsubscribe called on previously failed subscription', error);
};
}
})
.subscribe(subject);
return subject;
}
@@ -2,15 +2,26 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import createObservable from './subject';
jest.mock('@polkadot/api-provider/ws', () => class {
isConnected = () => true;
on = () => true;
send = () => true;
});
const Api = require('./index').default;
describe('subject', () => {
const params = [123, false];
let api;
let callback;
let section;
let subscription;
let observable;
beforeEach(() => {
api = new Api();
});
beforeEach(() => {
const subMethod = jest.fn((name, ...params) => {
callback = params.pop();
@@ -26,7 +37,7 @@ describe('subject', () => {
subMethod
};
observable = createObservable('subMethod', params, section);
observable = api.createSubject('subMethod', params, section);
});
afterEach(() => {
@@ -1,52 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import params from './params';
describe('params', () => {
let methods;
beforeEach(() => {
methods = {
array: {
params: [
{ name: 'test', type: ['Bytes'] }
],
type: 'Bytes'
},
blah: {
params: [
{ name: 'foo', type: 'Bytes' }
],
type: 'Bytes'
},
bleh: {
params: [
{ name: 'foo', type: 'Bytes', isOptional: true }
],
type: 'Bytes'
}
};
});
it('check against params', () => {
expect(
() => params(methods.blah.params, [])
).toThrow(/params expected/);
});
it('check against params (optional)', () => {
expect(
params(methods.bleh.params, [])
).toBeDefined();
});
it('formats array inputs correctly', () => {
expect(
params(methods.array.params, [[new Uint8Array([1]), new Uint8Array([2])]])
).toEqual([[
'0x01', '0x02'
]]);
});
});
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Interfaces } from '@polkadot/jsonrpc/types';
import { SectionItem } from '@polkadot/params/types';
import { Storages } from '@polkadot/storage/types';
import formatOutput from '@polkadot/api-format/output';
import decodeParams from '@polkadot/params/decode';
function formatStorageOutput (key: SectionItem<Storages>, result?: any): any {
return decodeParams(key.type, result, 'latest', true).value;
}
export default function formatResult (method: SectionItem<Interfaces>, params: Array<any>, inputs: Array<any>, result?: any): any {
if (method.type === 'StorageResult') {
return formatStorageOutput(inputs[0][0], result);
}
if (method.type === 'StorageResultSet') {
return params[0].map((key: string, index: number) => {
const input = inputs[0][index][0];
const { changes = [] }: { block: string, changes: Array<[string, string]> } = result || {};
const value = changes.find(([_key]) => key === _key);
if (!value) {
return undefined;
}
return formatStorageOutput(input, value[1]);
});
}
return formatOutput(method.type, result);
}
-54
View File
@@ -1,54 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import isFunction from '@polkadot/util/is/function';
jest.mock('@polkadot/jsonrpc', () => ({
test: {
public: {
blah: {
params: [
{ name: 'foo', type: 'Address' }
],
type: 'Address'
},
bleh: {
params: [],
type: 'Address'
},
pubsub: {
isSubscription: true,
params: [],
type: 'Address'
}
}
}
}));
const createInterface = require('./interface').default;
describe('createInterface', () => {
let container;
let provider;
beforeEach(() => {
provider = {
send: jest.fn((method, params) => {
return Promise.resolve(params[0]);
})
};
container = createInterface(provider, 'test');
});
it('adds the specified methods to the interface', () => {
expect(Object.keys(container)).toEqual(
['blah', 'bleh', 'pubsub']
);
});
it('had function calls for the attached methods', () => {
expect(isFunction(container.blah)).toEqual(true);
expect(isFunction(container.bleh)).toEqual(true);
});
});
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Interface$Sections } from '@polkadot/jsonrpc/types';
import { ProviderInterface } from '@polkadot/api-provider/types';
import { ApiInterface$Section } from '../types';
import interfaces from '@polkadot/jsonrpc/index';
import assert from '@polkadot/util/assert';
import isUndefined from '@polkadot/util/is/undefined';
import methodSend from './methodSend';
import methodSubscribe from './methodSubscribe';
export default function createInterface (provider: ProviderInterface, section: Interface$Sections): ApiInterface$Section {
const definition = interfaces[section];
assert(!isUndefined(definition), `Unable to find section '${section}`);
// @ts-ignore undefined check done in assert
const methods = definition.public;
return Object
.keys(methods)
.reduce((exposed, name) => {
const rpcName = `${section}_${name}`;
const def = methods[name];
exposed[name] = def.isSubscription
? methodSubscribe(provider, rpcName, def)
: methodSend(provider, rpcName, def);
return exposed;
}, {} as ApiInterface$Section);
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Interfaces } from '@polkadot/jsonrpc/types';
import { SectionItem } from '@polkadot/params/types';
import { ProviderInterface } from '@polkadot/api-provider/types';
import { ApiInterface$Section$Method } from '../types';
import formatInputs from '@polkadot/api-format/input';
import ExtError from '@polkadot/util/ext/error';
import signature from '@polkadot/params/signature';
import formatResult from './formatResult';
export default function createMethodSend (provider: ProviderInterface, rpcName: string, method: SectionItem<Interfaces>): ApiInterface$Section$Method {
const call = async (...values: Array<any>): Promise<any> => {
// TODO Warn on deprecated methods
try {
const params = formatInputs(method.params, values);
const result = await provider.send(rpcName, params);
return formatResult(method, params, values, result);
} catch (error) {
throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined);
}
};
return call as ApiInterface$Section$Method;
}
@@ -1,43 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Interfaces } from '@polkadot/jsonrpc/types';
import { SectionItem } from '@polkadot/params/types';
import { ProviderInterface, ProviderInterface$Callback } from '@polkadot/api-provider/types';
import { ApiInterface$Section$Method } from '../types';
import formatInputs from '@polkadot/api-format/input';
import signature from '@polkadot/params/signature';
import assert from '@polkadot/util/assert';
import ExtError from '@polkadot/util/ext/error';
import isFunction from '@polkadot/util/is/function';
import formatResult from './formatResult';
export default function methodSubscribe (provider: ProviderInterface, rpcName: string, method: SectionItem<Interfaces>): ApiInterface$Section$Method {
const unsubscribe = (subscriptionId: any): Promise<any> =>
provider.unsubscribe(rpcName, method.subscribe[1], subscriptionId);
const _call = async (...values: Array<any>): Promise<any> => {
try {
const cb: ProviderInterface$Callback = values.pop();
assert(isFunction(cb), `Expected callback in last position of params`);
const params = formatInputs(method.params, values);
const update = (error: Error | null, result?: any) => {
cb(error, formatResult(method, params, values, result));
};
return provider.subscribe(rpcName, method.subscribe[0], params, update);
} catch (error) {
throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined);
}
};
const call = _call as ApiInterface$Section$Method;
call.unsubscribe = unsubscribe;
return call;
}
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2017-2018 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Params } from '@polkadot/params/types';
import formatInputs from '@polkadot/api-format/input';
import assert from '@polkadot/util/assert';
export default function createParams (params: Params, values: Array<any>): Array<any> {
const required = params.filter(({ isOptional }) => !isOptional);
const optionalText = params.length
? ` (${(params.length - required.length) || 'none'} optional)`
: '';
assert(values.length >= required.length && values.length <= params.length, `${params.length || 'no'} params expected${optionalText}, found ${values.length} instead`);
return formatInputs(params, values);
}
@@ -4,7 +4,7 @@
import storage from '@polkadot/storage';
import Api from '../index';
import Api from './index';
const ADDR_ONE = '5EhmTa7fL6SdjgKXo9g6hetR6nHnRAmrtisoGFWEESjzECtY';
const ADDR_TWO = '5FjbQAHSptRU4Q7Py6zDsRnE7UkpZikJVg5nm1tmu3WuZXKd';
+109 -7
View File
@@ -2,11 +2,21 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ProviderInterface } from '@polkadot/api-provider/types';
import { ApiInterface, ApiInterface$Section } from './types';
import { ProviderInterface, ProviderInterface$Callback } from '@polkadot/api-provider/types';
import { Interfaces, Interface$Sections } from '@polkadot/jsonrpc/types';
import { SectionItem } from '@polkadot/params/types';
import { Storages } from '@polkadot/storage/types';
import { ApiInterface, ApiInterface$Section, ApiInterface$Section$Method } from './types';
import formatInputs from '@polkadot/api-format/input';
import formatOutput from '@polkadot/api-format/output';
import interfaces from '@polkadot/jsonrpc/index';
import decodeParams from '@polkadot/params/decode';
import signature from '@polkadot/params/signature';
import assert from '@polkadot/util/assert';
import ExtError from '@polkadot/util/ext/error';
import isFunction from '@polkadot/util/is/function';
import createInterface from './create/interface';
import isUndefined from '@polkadot/util/is/undefined';
/**
* @example
@@ -14,6 +24,7 @@ import createInterface from './create/interface';
*
* import Api from '@polkadot/api';
* import WsProvider from '@polkadot/api-provider/ws';
*
* const provider = new WsProvider('http://127.0.0.1:9944');
* const api = new Api(provider);
* ```
@@ -35,9 +46,100 @@ export default class Api implements ApiInterface {
this._provider = provider;
this.author = createInterface(this._provider, 'author');
this.chain = createInterface(this._provider, 'chain');
this.state = createInterface(this._provider, 'state');
this.system = createInterface(this._provider, 'system');
this.author = this.createInterface('author');
this.chain = this.createInterface('chain');
this.state = this.createInterface('state');
this.system = this.createInterface('system');
}
private createInterface (section: Interface$Sections): ApiInterface$Section {
const definition = interfaces[section];
assert(!isUndefined(definition), `Unable to find section '${section}`);
// @ts-ignore undefined check done in assert
const methods = definition.public;
return Object
.keys(methods)
.reduce((exposed, name) => {
const rpcName = `${section}_${name}`;
const def = methods[name];
exposed[name] = def.isSubscription
? this.createMethodSubscribe(rpcName, def)
: this.createMethodSend(rpcName, def);
return exposed;
}, {} as ApiInterface$Section);
}
private createMethodSend (rpcName: string, method: SectionItem<Interfaces>): ApiInterface$Section$Method {
const call = async (...values: Array<any>): Promise<any> => {
// TODO Warn on deprecated methods
try {
const params = formatInputs(method.params, values);
const result = await this._provider.send(rpcName, params);
return this.formatResult(method, params, values, result);
} catch (error) {
throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined);
}
};
return call as ApiInterface$Section$Method;
}
private createMethodSubscribe (rpcName: string, method: SectionItem<Interfaces>): ApiInterface$Section$Method {
const unsubscribe = (subscriptionId: any): Promise<any> =>
this._provider.unsubscribe(rpcName, method.subscribe[1], subscriptionId);
const _call = async (...values: Array<any>): Promise<any> => {
try {
const cb: ProviderInterface$Callback = values.pop();
assert(isFunction(cb), `Expected callback in last position of params`);
const params = formatInputs(method.params, values);
const update = (error: Error | null, result?: any) => {
cb(error, this.formatResult(method, params, values, result));
};
return this._provider.subscribe(rpcName, method.subscribe[0], params, update);
} catch (error) {
throw new ExtError(`${signature(method)}:: ${error.message}`, (error as ExtError).code, undefined);
}
};
const call = _call as ApiInterface$Section$Method;
call.unsubscribe = unsubscribe;
return call;
}
private formatResult (method: SectionItem<Interfaces>, params: Array<any>, inputs: Array<any>, result?: any): any {
if (method.type === 'StorageResult') {
return this.formatStorageOutput(inputs[0][0], result);
}
if (method.type === 'StorageResultSet') {
return params[0].map((key: string, index: number) => {
const input = inputs[0][index][0];
const { changes = [] }: { block: string, changes: Array<[string, string]> } = result || {};
const value = changes.find(([_key]) => key === _key);
if (!value) {
return undefined;
}
return this.formatStorageOutput(input, value[1]);
});
}
return formatOutput(method.type, result);
}
private formatStorageOutput (key: SectionItem<Storages>, result?: any): any {
return decodeParams(key.type, result, 'latest', true).value;
}
}
@@ -2,9 +2,10 @@
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import createMethod from './methodSend';
import Api from './index';
describe('methodCall', () => {
describe('methodSend', () => {
let api;
let methods;
let provider;
@@ -29,10 +30,12 @@ describe('methodCall', () => {
return Promise.resolve(params[0]);
})
};
api = new Api(provider);
});
it('wraps errors with the call signature', () => {
const method = createMethod(provider, 'test_blah', methods.blah);
const method = api.createMethodSend('test_blah', methods.blah);
return method().catch((error) => {
expect(error.message).toMatch(/blah \(foo: Bytes\): Bytes/);
@@ -40,7 +43,7 @@ describe('methodCall', () => {
});
it('checks for mismatched parameters', () => {
const method = createMethod(provider, 'test_bleh', methods.bleh);
const method = api.createMethodSend('test_bleh', methods.bleh);
return method(1).catch((error) => {
expect(error.message).toMatch(/no params expected, found 1 instead/);
@@ -48,7 +51,7 @@ describe('methodCall', () => {
});
it('calls the provider with the correct parameters', () => {
const method = createMethod(provider, 'test_blah', methods.blah);
const method = api.createMethodSend('test_blah', methods.blah);
return method(new Uint8Array([0x12, 0x34])).then(() => {
expect(provider.send).toHaveBeenCalledWith('test_blah', ['0x1234']);