First stage renames, rpc-{core,provider,rx} (#220)
* Docs renames (& removal for re-gen) * Rename api, api-provider & api-rx * Apoi -> Rpc, RxApi -> RpcRx (classes) * Add .nodoc * Bump version * Link updates (summary) * Cleanup deleted (master version) * Deleted re-instated * More... ;) * Types... * Revert .nodoc
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-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('@polkadot/rpc-provider/ws', () => class {
|
||||
isConnected = () => true;
|
||||
on = () => true;
|
||||
send = () => true;
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
subMethod.unsubscribe = jest.fn(() => {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
|
||||
const resMethod = jest.fn((name, ...params) => {
|
||||
return Promise.resolve(12345);
|
||||
});
|
||||
|
||||
section = {
|
||||
resMethod,
|
||||
subMethod
|
||||
};
|
||||
|
||||
creator = api.createCachedObservable('test', 'subMethod', section);
|
||||
});
|
||||
|
||||
it('creates a single observable', () => {
|
||||
creator(123).subscribe((value) => {});
|
||||
|
||||
expect(
|
||||
section.subMethod
|
||||
).toHaveBeenCalledWith(123, expect.anything());
|
||||
});
|
||||
|
||||
it('creates a single observable (multiple calls)', () => {
|
||||
const observable1 = creator(123);
|
||||
|
||||
observable1.subscribe((value) => {});
|
||||
|
||||
const observable2 = creator(123);
|
||||
|
||||
observable2.subscribe((value) => {});
|
||||
|
||||
expect(
|
||||
observable2
|
||||
).toEqual(observable1);
|
||||
});
|
||||
|
||||
it('creates multiple observers for different values', () => {
|
||||
const observable1 = creator(123);
|
||||
|
||||
observable1.subscribe((value) => {});
|
||||
|
||||
const observable2 = creator(456);
|
||||
|
||||
observable2.subscribe((value) => {});
|
||||
|
||||
expect(
|
||||
observable2
|
||||
).not.toEqual(observable1);
|
||||
});
|
||||
|
||||
it('functions as an subject', (done) => {
|
||||
const subject = creator(123);
|
||||
|
||||
subject.subscribe((value) => {
|
||||
if (value) {
|
||||
expect(value).toEqual('test');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
subject.next('test');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
const WS_URL = 'ws://127.0.0.1:9944';
|
||||
|
||||
export default {
|
||||
WS_URL
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-rx 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/rpc-provider/ws', () => class {
|
||||
isConnected = () => true;
|
||||
on = () => true;
|
||||
send = () => true;
|
||||
});
|
||||
|
||||
const Api = require('./index').default;
|
||||
|
||||
describe('Api', () => {
|
||||
let api;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new Api();
|
||||
});
|
||||
|
||||
it('creates an instance with all sections', () => {
|
||||
expect(
|
||||
Object
|
||||
.keys(api)
|
||||
.filter((key) =>
|
||||
!key.startsWith('_')
|
||||
)
|
||||
).toEqual([
|
||||
'author', 'chain', 'state', 'system'
|
||||
]);
|
||||
});
|
||||
|
||||
it('has isConnected', () => {
|
||||
expect(
|
||||
isFunction(api.isConnected)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { RpcInterface, RpcInterface$Section } from '@polkadot/rpc-core/types';
|
||||
import { ProviderInterface } from '@polkadot/rpc-provider/types';
|
||||
import { RxRpcInterface, RxRpcInterface$Section } from './types';
|
||||
|
||||
import { BehaviorSubject, Observable, Subscriber, from } from 'rxjs';
|
||||
import Rpc from '@polkadot/rpc-core/index';
|
||||
import Ws from '@polkadot/rpc-provider/ws';
|
||||
import isFunction from '@polkadot/util/is/function';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
import defaults from './defaults';
|
||||
|
||||
type CachedMap = {
|
||||
[index: string]: {
|
||||
[index: string]: BehaviorSubject<any>
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name RpcRx
|
||||
* @summary The RxJS API is a wrapper around the API.
|
||||
* @description It allows wrapping API components with observables using RxJS.
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Rpc from '@polkadot/rpc-rx';
|
||||
* import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
*
|
||||
* const provider = new WsProvider('http://127.0.0.1:9944');
|
||||
* const api = new Rpc(provider);
|
||||
* ```
|
||||
*/
|
||||
export default class RpcRx implements RxRpcInterface {
|
||||
private _api: RpcInterface;
|
||||
private _cacheMap: CachedMap;
|
||||
private _isConnected: BehaviorSubject<boolean>;
|
||||
readonly author: RxRpcInterface$Section;
|
||||
readonly chain: RxRpcInterface$Section;
|
||||
readonly state: RxRpcInterface$Section;
|
||||
readonly system: RxRpcInterface$Section;
|
||||
|
||||
/**
|
||||
* @param {ProviderInterface} provider An API provider using HTTP or WebSocket
|
||||
*/
|
||||
constructor (provider: ProviderInterface = new Ws(defaults.WS_URL)) {
|
||||
this._api = new Rpc(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 = this.createInterface('author', this._api.author);
|
||||
this.chain = this.createInterface('chain', this._api.chain);
|
||||
this.state = this.createInterface('state', this._api.state);
|
||||
this.system = this.createInterface('system', this._api.system);
|
||||
}
|
||||
|
||||
isConnected (): BehaviorSubject<boolean> {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
private createInterface (sectionName: string, section: RpcInterface$Section): RxRpcInterface$Section {
|
||||
return Object
|
||||
.keys(section)
|
||||
.filter((name) => !['subscribe', 'unsubscribe'].includes(name))
|
||||
.reduce((observables, name) => {
|
||||
observables[name] = this.createObservable(`${sectionName}_${name}`, name, section);
|
||||
|
||||
return observables;
|
||||
}, ({} as RxRpcInterface$Section));
|
||||
}
|
||||
|
||||
private createObservable (subName: string, name: string, section: RpcInterface$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: RpcInterface$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: RpcInterface$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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-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('@polkadot/rpc-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();
|
||||
|
||||
return Promise.resolve(12345);
|
||||
});
|
||||
|
||||
subMethod.unsubscribe = jest.fn(() => {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
|
||||
section = {
|
||||
subMethod
|
||||
};
|
||||
|
||||
observable = api.createSubject('subMethod', params, section);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
it('subscribes via the api section', (done) => {
|
||||
observable.subscribe((value) => {
|
||||
if (value) {
|
||||
expect(
|
||||
section.subMethod
|
||||
).toHaveBeenCalledWith(123, false, expect.anything());
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
callback(null, 'test');
|
||||
});
|
||||
|
||||
it('returns the observable value', (done) => {
|
||||
subscription = observable.subscribe((value) => {
|
||||
if (value) {
|
||||
expect(value).toEqual('test');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
callback(null, 'test');
|
||||
});
|
||||
|
||||
it('ignores errors, returns the observable value', (done) => {
|
||||
subscription = observable.subscribe((value) => {
|
||||
if (value) {
|
||||
expect(value).toEqual('test');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
callback(new Error('error'));
|
||||
callback(null, 'test');
|
||||
});
|
||||
});
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import interfaces from '@polkadot/jsonrpc/types';
|
||||
|
||||
export type RxRpcInterface$Method = (...params: Array<any>) => Observable<any> | BehaviorSubject<any>;
|
||||
|
||||
export type RxRpcInterface$Section = {
|
||||
[index: string]: RxRpcInterface$Method
|
||||
};
|
||||
|
||||
export type RxRpcInterface = {
|
||||
readonly author: RxRpcInterface$Section;
|
||||
readonly chain: RxRpcInterface$Section;
|
||||
readonly state: RxRpcInterface$Section;
|
||||
readonly system: RxRpcInterface$Section;
|
||||
|
||||
isConnected: () => BehaviorSubject<boolean>
|
||||
}
|
||||
Reference in New Issue
Block a user