diff --git a/README.md b/README.md index 59aeda525f..9cc1b32a95 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ The API is split up into a number of internal packages - - [@polkadot/api-derive](packages/api-derive/) Derived results that are injected into the API, allowing for combinations of various query results (only used internally and exposed on the Api instances via `api.derive.*`) - [@polkadot/rpc-core](packages/rpc-core/) Wrapper around all [JSON-RPC methods](https://polkadot.js.org/api/METHODS_RPC.html) exposed by a Polkadot network client - [@polkadot/rpc-provider](packages/rpc-provider/) Providers for connecting to nodes, including WebSockets and Http -- [@polkadot/rpc-rx](packages/rpc-rx/) A RxJs Observable wrapper around [@polkadot/rpc-provider](packages/rpc-provider) Type definitions for interfaces as exposed by Polkadot & Substrate clients - diff --git a/jest.config.js b/jest.config.js index 1fdc16e75b..abfe5aec2e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,7 @@ module.exports = Object.assign({}, config, { '@polkadot/api-contract(.*)$': '/packages/api-contract/src/$1', '@polkadot/api-derive(.*)$': '/packages/api-derive/src/$1', '@polkadot/api(.*)$': '/packages/api/src/$1', - '@polkadot/rpc-(core|provider|rx)(.*)$': '/packages/rpc-$1/src/$2', + '@polkadot/rpc-(core|provider)(.*)$': '/packages/rpc-$1/src/$2', '@polkadot/extrinsics(.*)$': '/packages/type-extrinsics/src/$1', '@polkadot/jsonrpc(.*)$': '/packages/type-jsonrpc/src/$1', '@polkadot/storage(.*)$': '/packages/type-storage/src/$1', @@ -17,7 +17,6 @@ module.exports = Object.assign({}, config, { '/packages/api-contract/build', '/packages/rpc-core/build', '/packages/rpc-provider/build', - '/packages/rpc-rx/build', '/packages/type-extrinsics/build', '/packages/type-jsonrpc/build', '/packages/type-storage/build/', diff --git a/packages/api/package.json b/packages/api/package.json index 7d35246b0a..6a8e255aac 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -30,7 +30,6 @@ "@polkadot/api-derive": "^0.82.0-beta.13", "@polkadot/extrinsics": "^0.82.0-beta.13", "@polkadot/rpc-provider": "^0.82.0-beta.13", - "@polkadot/rpc-rx": "^0.82.0-beta.13", "@polkadot/storage": "^0.82.0-beta.13", "@polkadot/types": "^0.82.0-beta.13", "@polkadot/util-crypto": "^0.94.0-beta.3" diff --git a/packages/api/src/Base.ts b/packages/api/src/Base.ts index 320f93cad4..45ea72542c 100644 --- a/packages/api/src/Base.ts +++ b/packages/api/src/Base.ts @@ -17,8 +17,8 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import decorateDerive from '@polkadot/api-derive'; import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata'; -import RpcBase from '@polkadot/rpc-core'; -import RpcRx from '@polkadot/rpc-rx'; +import RpcCore from '@polkadot/rpc-core'; +import { WsProvider } from '@polkadot/rpc-provider'; import storageFromMeta from '@polkadot/storage/fromMetadata'; import { Event, getTypeRegistry, Hash, Metadata, Method, RuntimeVersion, Null, VectorAny } from '@polkadot/types'; import Linkage, { LinkageResult } from '@polkadot/types/codec/Linkage'; @@ -66,13 +66,13 @@ export default abstract class ApiBase { private _eventemitter: EventEmitter; private _extrinsics?: SubmittableExtrinsics; private _genesisHash?: Hash; + protected _isConnected: BehaviorSubject; private _isReady: boolean = false; protected readonly _options: ApiOptions; private _query?: QueryableStorage; private _queryMulti: QueryableStorageMulti; private _rpc: DecoratedRpc; - protected _rpcBase: RpcBase; // FIXME These two could be merged https://github.com/polkadot-js/api/issues/642 - protected _rpcRx: RpcRx; // FIXME These two could be merged https://github.com/polkadot-js/api/issues/642 + protected _rpcCore: RpcCore; private _runtimeMetadata?: Metadata; private _runtimeVersion?: RuntimeVersion; private _rx: Partial = {}; @@ -101,19 +101,19 @@ export default abstract class ApiBase { ? { provider } as ApiOptions : provider as ApiOptions; const thisProvider = options.source - ? options.source._rpcBase._provider.clone() - : options.provider; + ? options.source._rpcCore.provider.clone() + : (options.provider || new WsProvider()); this._options = options; this._type = type; this._eventemitter = new EventEmitter(); - this._rpcBase = new RpcBase(thisProvider); + this._rpcCore = new RpcCore(thisProvider); + this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected()); assert(this.hasSubscriptions, 'Api can only be used with a provider supporting subscriptions'); - this._rpcRx = new RpcRx(this._rpcBase._provider); - this._rpc = this.decorateRpc(this._rpcRx, this.decorateMethod); - this._rx.rpc = this.decorateRpc(this._rpcRx, rxDecorateMethod); + this._rpc = this.decorateRpc(this._rpcCore, this.decorateMethod); + this._rx.rpc = this.decorateRpc(this._rpcCore, rxDecorateMethod); this._queryMulti = this.decorateMulti(this.decorateMethod); this._rx.queryMulti = this.decorateMulti(rxDecorateMethod); this._rx.signer = options.signer; @@ -139,7 +139,7 @@ export default abstract class ApiBase { * @description `true` when subscriptions are supported */ get hasSubscriptions (): boolean { - return this._rpcBase._provider.hasSubscriptions; + return this._rpcCore.provider.hasSubscriptions; } /** @@ -285,7 +285,7 @@ export default abstract class ApiBase { * @description Disconnect from the underlying provider, halting all comms */ disconnect (): void { - this._rpcBase.disconnect(); + this._rpcCore.disconnect(); } /** @@ -399,8 +399,9 @@ export default abstract class ApiBase { private init (): void { let healthTimer: NodeJS.Timeout | null = null; - this._rpcBase._provider.on('disconnected', () => { + this._rpcCore.provider.on('disconnected', () => { this.emit('disconnected'); + this._isConnected.next(false); if (healthTimer) { clearInterval(healthTimer); @@ -408,12 +409,13 @@ export default abstract class ApiBase { } }); - this._rpcBase._provider.on('error', (error) => { + this._rpcCore.provider.on('error', (error) => { this.emit('error', error); }); - this._rpcBase._provider.on('connected', async () => { + this._rpcCore.provider.on('connected', async () => { this.emit('connected'); + this._isConnected.next(true); try { const [hasMeta, cryptoReady] = await Promise.all([ @@ -428,7 +430,7 @@ export default abstract class ApiBase { } healthTimer = setInterval(() => { - this._rpcRx.system.health().toPromise().catch(() => { + this._rpcCore.system.health().toPromise().catch(() => { // ignore }); }, KEEPALIVE_INTERVAL); @@ -449,14 +451,14 @@ export default abstract class ApiBase { // just use the values from the source instance provided if (!this._options.source || !this._options.source._isReady) { [this._genesisHash, this._runtimeVersion] = await Promise.all([ - this._rpcBase.chain.getBlockHash(0), - this._rpcBase.chain.getRuntimeVersion() + this._rpcCore.chain.getBlockHash(0).toPromise(), + this._rpcCore.chain.getRuntimeVersion().toPromise() ]); const metadataKey = `${this._genesisHash}-${(this._runtimeVersion as RuntimeVersion).specVersion}`; if (metadataKey in metadata) { this._runtimeMetadata = new Metadata(metadata[metadataKey]); } else { - this._runtimeMetadata = await this._rpcBase.state.getMetadata(); + this._runtimeMetadata = await this._rpcCore.state.getMetadata().toPromise(); } // get unique types & validate @@ -503,7 +505,7 @@ export default abstract class ApiBase { return output; } - private decorateRpc (rpc: RpcRx, decorateMethod: ApiBase['decorateMethod']): DecoratedRpc { + private decorateRpc (rpc: RpcCore, decorateMethod: ApiBase['decorateMethod']): DecoratedRpc { return ['author', 'chain', 'state', 'system'].reduce((result, _sectionName) => { const sectionName = _sectionName as keyof DecoratedRpc; @@ -528,7 +530,7 @@ export default abstract class ApiBase { : [arg.creator] as any ); - return this._rpcRx.state + return this._rpcCore.state .subscribeStorage(mapped) .pipe(map((results) => new VectorAny(...results))); }); @@ -579,7 +581,8 @@ export default abstract class ApiBase { : decorateMethod( (...args: Array) => { - return this._rpcRx.state + return this._rpcCore.state + // Unfortunately for one-shot calls we also use .subscribeStorage here .subscribeStorage([ creator.meta.type.isDoubleMap ? [creator, args] @@ -598,7 +601,7 @@ export default abstract class ApiBase { decorated.creator = creator; decorated.at = decorateMethod( - (hash: Hash, arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorage( + (hash: Hash, arg1?: CodecArg, arg2?: CodecArg) => this._rpcCore.state.getStorage( creator.meta.type.isDoubleMap ? [creator, [arg1, arg2]] : [creator, arg1], @@ -606,7 +609,7 @@ export default abstract class ApiBase { ); decorated.hash = decorateMethod( - (arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorageHash( + (arg1?: CodecArg, arg2?: CodecArg) => this._rpcCore.state.getStorageHash( creator.meta.type.isDoubleMap ? [creator, [arg1, arg2]] : [creator, arg1]) @@ -618,13 +621,13 @@ export default abstract class ApiBase { // When using double map storage function, user need to path double map key as an array decorated.multi = decorateMethod( (args: Array) => - this._rpcRx.state + this._rpcCore.state .subscribeStorage(args.map((arg: CodecArg[] | CodecArg) => [creator, arg])) .pipe(map((results) => new VectorAny(...results))) ); decorated.size = decorateMethod( - (arg1?: CodecArg, arg2?: CodecArg) => this._rpcRx.state.getStorageSize( + (arg1?: CodecArg, arg2?: CodecArg) => this._rpcCore.state.getStorageSize( creator.meta.type.isDoubleMap ? [creator, [arg1, arg2]] : [creator, arg1]) @@ -642,7 +645,7 @@ export default abstract class ApiBase { // entries can be re-linked in the middle of a list, we subscribe here to make // sure we catch any updates, no matter the list position const getNext = (key: Codec): Observable => { - return this._rpcRx.state.subscribeStorage([[method, key]]) + return this._rpcCore.state.subscribeStorage([[method, key]]) .pipe( switchMap(([data]: [[Codec, Linkage]]) => { const linkage = data[1]; @@ -703,7 +706,7 @@ export default abstract class ApiBase { // this handles the case where the head changes effectively, i.e. a new entry // appears at the top of the list, the new getNext gets kicked off return decorateMethod( - () => this._rpcRx.state + () => this._rpcCore.state .subscribeStorage([method.headKey]) .pipe( switchMap(([key]: Array) => { diff --git a/packages/api/src/rx/Api.spec.ts b/packages/api/src/rx/Api.spec.ts new file mode 100644 index 0000000000..dad3506dc1 --- /dev/null +++ b/packages/api/src/rx/Api.spec.ts @@ -0,0 +1,17 @@ +// Copyright 2017-2019 @polkadot/api authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { isObservable } from '@polkadot/util'; + +import ApiRx from './Api'; + +describe('ApiRx', () => { + it('has isConnected', () => { + const api = new ApiRx(); + + expect( + isObservable(api.isConnected) + ).toBe(true); + }); +}); diff --git a/packages/api/src/rx/Api.ts b/packages/api/src/rx/Api.ts index baacfa516b..f7aded9151 100644 --- a/packages/api/src/rx/Api.ts +++ b/packages/api/src/rx/Api.ts @@ -178,7 +178,7 @@ export default class ApiRx extends ApiBase<'rxjs'> { * @description Observable that carries the connected state for the provider. Results in a boolean flag that is true/false based on the connectivity. */ get isConnected (): Observable { - return this._rpcRx.isConnected(); + return this._isConnected; } /** diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 991b8ebc35..4b9c322ab8 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -4,8 +4,7 @@ import { Observable } from 'rxjs'; import { DeriveCustom } from '@polkadot/api-derive'; -import { ProviderInterface } from '@polkadot/rpc-provider/types'; -import { RpcRxInterface$Events } from '@polkadot/rpc-rx/types'; +import { ProviderInterface, ProviderInterface$Emitted } from '@polkadot/rpc-provider/types'; import { Hash, RuntimeVersion, u64 as U64 } from '@polkadot/types'; import { AnyFunction, Callback, Codec, CodecArg, IExtrinsic, RegistryTypes, SignatureOptions } from '@polkadot/types/types'; import { MethodFunction } from '@polkadot/types/primitive/Method'; @@ -206,7 +205,7 @@ export interface ApiInterface$Rx { signer?: Signer; } -export type ApiInterface$Events = RpcRxInterface$Events | 'ready'; +export type ApiInterface$Events = ProviderInterface$Emitted | 'ready'; export type ApiTypes = 'promise' | 'rxjs'; diff --git a/packages/api/test/e2e/promise-tx.spec.ts b/packages/api/test/e2e/promise-tx.spec.ts index 6412cc8c59..3872050e7d 100644 --- a/packages/api/test/e2e/promise-tx.spec.ts +++ b/packages/api/test/e2e/promise-tx.spec.ts @@ -192,10 +192,12 @@ describe.skip('Promise e2e transactions', () => { return ( api.rpc.chain.subscribeNewHead(async (header: Header) => { if (header.blockNumber.toNumber() === eraDeath - 1) { - const hash = await ex.signAndSend(keyring.alice, { blockHash: eraHash, era: exERA, nonce } as any); - - expect(hash).toBeUndefined(); - done(); + try { + await ex.signAndSend(keyring.alice, { blockHash: eraHash, era: exERA, nonce } as any); + } catch (error) { + expect(error.message).toMatch(/1010: Invalid Transaction \(0\)/); + done(); + } } }) ); diff --git a/packages/rpc-core/README.md b/packages/rpc-core/README.md index 3747fe7ad4..69f1adfa28 100644 --- a/packages/rpc-core/README.md +++ b/packages/rpc-core/README.md @@ -17,53 +17,58 @@ import Rpc from '@polkadot/rpc-core'; import WsProvider from '@polkadot/rpc-provider/ws'; const provider = new WsProvider('http://127.0.0.1:9944'); -const api = new Rpc(provider); +const rpc = new Rpc(provider); ``` Retrieving the block header object for a given block header hash (a 0x-prefixed hex string with length of 64) - ```js -api.chain +rpc.chain .getHeader('0x1234567890') - .then((header) => console.log(header)) - .catch((error) => console.error(error)); + .subscribe( + (header) => console.log(header), + (error) => console.error('error:', error) + ); ``` Retrieving the best block number, parent hash, state root hash, extrinsics root hash, and digest (once-off) - ```js -api.chain +rpc.chain .getHead() - .then((headerHash) => { - return api.chain.getHeader(headerHash); - }) - .then((header) => { - console.log(`best #${header.blockNumber.toString()}`); - console.log(`parentHash: ${header.parentHash.toString()}`); - console.log(`stateRoot: ${header.stateRoot.toString()}`); - console.log(`extrinsicsRoot: ${header.extrinsicsRoot.toString()}`); - console.log(`digest: ${header.digest.toString()}`); - }) - .catch((error) => { - console.error('error:', error); - }); + .pipe( + switchMap((headerHash) => { + return rpc.chain.getHeader(headerHash); + }) + ) + .subscribe( + (header) => { + console.log(`best #${header.blockNumber.toString()}`); + console.log(`parentHash: ${header.parentHash.toString()}`); + console.log(`stateRoot: ${header.stateRoot.toString()}`); + console.log(`extrinsicsRoot: ${header.extrinsicsRoot.toString()}`); + console.log(`digest: ${header.digest.toString()}`); + }, + (error) => { + console.error('error:', error); + } + ); ``` Retrieving best header via subscription - ```js api.chain - .subscribeNewHead((header) => { - console.log(`best #${header.blockNumber}`); - }) - .then((subscriptionId) => { - console.log(`subscriptionId: ${subscriptionId}`); - // id for the subscription, can unsubscribe via - // api.chain.subscribeNewHead.unsubscribe(subscriptionId); - }) - .catch((error) => { - console.error('error subscribing:', error); - }); +rpc + .subscribeNewHead() + .subscribe( + (header) => { + console.log(`best #${header.blockNumber}`); + }, + (error) => { + console.error('error subscribing:', error); + } + ); ``` ## Classes diff --git a/packages/rpc-core/package.json b/packages/rpc-core/package.json index c1b186416b..37edec9c0e 100644 --- a/packages/rpc-core/package.json +++ b/packages/rpc-core/package.json @@ -30,6 +30,8 @@ "@polkadot/jsonrpc": "^0.82.0-beta.13", "@polkadot/rpc-provider": "^0.82.0-beta.13", "@polkadot/types": "^0.82.0-beta.13", - "@polkadot/util": "^0.94.0-beta.3" + "@polkadot/util": "^0.94.0-beta.3", + "memoizee": "^0.4.14", + "rxjs": "^6.5.2" } } diff --git a/packages/rpc-core/src/cached.spec.ts b/packages/rpc-core/src/cached.spec.ts new file mode 100644 index 0000000000..a6d0ce564e --- /dev/null +++ b/packages/rpc-core/src/cached.spec.ts @@ -0,0 +1,78 @@ +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import testingPairs from '@polkadot/keyring/testingPairs'; +import MockProvider from '@polkadot/rpc-provider/mock'; +import { AccountId } from '@polkadot/types'; + +import Rpc from '.'; + +describe('Cached Observables', () => { + let rpc: Rpc; + const keyring = testingPairs(); + + beforeEach(() => { + rpc = new Rpc(new MockProvider()); + }); + + it('creates a single observable for subscriptions (multiple calls)', () => { + const observable1 = rpc.chain.subscribeNewHead(123); + const observable2 = rpc.chain.subscribeNewHead(123); + + expect(observable2).toBe(observable1); + }); + + it('creates a single observable for subscriptions (multiple calls, no arguments)', () => { + const observable1 = rpc.chain.subscribeNewHead(); + const observable2 = rpc.chain.subscribeNewHead(); + + expect(observable2).toBe(observable1); + }); + + it('creates a single observable (multiple calls, different arguments that should be cached together)', () => { + const observable1 = rpc.chain.subscribeNewHead(keyring.alice.address); + const observable2 = rpc.chain.subscribeNewHead(new AccountId(keyring.alice.address)); + + expect(observable2).toBe(observable1); + }); + + it('creates multiple observables for different values', () => { + const observable1 = rpc.chain.subscribeNewHead(123); + const observable2 = rpc.chain.subscribeNewHead(456); + + expect(observable2).not.toBe(observable1); + }); + + it('clears cache if there are no more subscribers', () => { + const observable1 = rpc.chain.subscribeNewHead(); + const observable2 = rpc.chain.subscribeNewHead(); + + expect(observable1).toBe(observable2); + + const sub1 = observable1.subscribe(); + const sub2 = observable2.subscribe(); + + sub1.unsubscribe(); + sub2.unsubscribe(); + + // No more subscribers, now create a new observable + const observable3 = rpc.chain.subscribeNewHead(); + + expect(observable3).not.toBe(observable1); + }); + + it('creates different observables for different methods but same arguments', () => { + const observable1 = rpc.chain.subscribeNewHead(123); + const observable2 = rpc.state.subscribeStorage(123); + + expect(observable2).not.toBe(observable1); + }); + + it('creates multiple observables for one-shots', () => { + const observable1 = rpc.system.chain(123); + const observable2 = rpc.system.chain(123); + + expect(observable2).not.toBe(observable1); + }); +}); diff --git a/packages/rpc-core/src/formatting.spec.ts b/packages/rpc-core/src/formatting.spec.ts index 7264e99684..a229020654 100644 --- a/packages/rpc-core/src/formatting.spec.ts +++ b/packages/rpc-core/src/formatting.spec.ts @@ -16,11 +16,11 @@ import Api from '.'; const ADDR_ONE = '5DkQbYAExs3M2sZgT1Ec3mKfZnAQCL4Dt9beTCknkCUn5jzo'; const ADDR_TWO = '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCtGwF'; const BALANCE_KEYS = [ - /* v3- */ '0x4af2c53fce3ec33c6ccccf22e926f1a7', - /* v4+ */ '0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad' + /* v3-, using xxHash */ '0x4af2c53fce3ec33c6ccccf22e926f1a7', + /* v4+, using blake2_256 */ '0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad' ]; -function formattingTests (version: string, storage: Storage, encodedValues: [String, String]) { +function formattingTests (version: string, storage: Storage, encodedValues: [string, string]) { const [ENC_ONE, ENC_TWO] = encodedValues; describe(`formatting with Metadata ${version}`, () => { @@ -36,8 +36,8 @@ function formattingTests (version: string, storage: Storage, encodedValues: [Str : null ) ), - subscribe: jest.fn((type, method, params, subscription) => - subscription(null, { + subscribe: jest.fn((type, method, params, cb) => + cb(null, { block: '0x1234', changes: [ [ENC_ONE, '0x01020000000000000000000000000000'], @@ -49,10 +49,10 @@ function formattingTests (version: string, storage: Storage, encodedValues: [Str api = new Api(provider); }); - it('encodes key (with params), decoding response', () => { - return api.state + it('encodes key (with params), decoding response', (done) => { + api.state .getStorage([storage.balances.freeBalance, ADDR_ONE]) - .then((value) => { + .subscribe((value) => { expect( provider.send ).toHaveBeenCalledWith( @@ -60,42 +60,44 @@ function formattingTests (version: string, storage: Storage, encodedValues: [Str [ENC_ONE] ); expect(value.toNumber()).toEqual(513); + done(); }); }); - it('returns the fallback result on not-found values', () => { - return api.state + it('returns the fallback result on not-found values', (done) => { + api.state .getStorage([storage.system.accountNonce, ADDR_ONE]) - .then((value) => { + .subscribe((value) => { expect(value.toHex()).toEqual('0x0000000000000000'); + done(); }); }); it('encodes multiple keys, decoding multiple results', (done) => { - return api.state.subscribeStorage( + api.state.subscribeStorage( [ [storage.balances.freeBalance, ADDR_ONE], [storage.balances.freeBalance, ADDR_TWO] - ], - (value: any) => { - console.error(value); + ] + ).subscribe((value: any) => { + console.error(value); - expect( - provider.subscribe - ).toHaveBeenCalledWith( - 'state_storage', - 'state_subscribeStorage', - [[ENC_ONE, ENC_TWO]], - expect.anything() - ); - expect( - value.map((balance: BN) => - balance.toNumber() - ) - ).toEqual([513, 258]); + expect( + provider.subscribe + ).toHaveBeenCalledWith( + 'state_storage', + 'state_subscribeStorage', + [[ENC_ONE, ENC_TWO]], + expect.anything() + ); + expect( + value.map((balance: BN) => + balance.toNumber() + ) + ).toEqual([513, 258]); - done(); - }); + done(); + }); }); }); } diff --git a/packages/rpc-core/src/index.spec.ts b/packages/rpc-core/src/index.spec.ts index 60e8dcad22..23fc6e8a25 100644 --- a/packages/rpc-core/src/index.spec.ts +++ b/packages/rpc-core/src/index.spec.ts @@ -2,38 +2,25 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import Api from '.'; +import MockProvider from '@polkadot/rpc-provider/mock'; + +import Rpc from '.'; describe('Api', () => { - let api: Api; - let provider: any; - let sendSpy: any; - - beforeEach(() => { - provider = { - send: (method: any, params: Array) => { - return Promise.resolve(params[0]); - } - }; - sendSpy = jest.spyOn(provider, 'send'); - api = new Api(provider); - }); - - afterEach(() => { - sendSpy.mockRestore(); - }); - it('requires a provider with a send method', () => { expect( - () => new Api({} as any) + () => new Rpc({} as any) ).toThrow(/Expected Provider/); }); - it('sets up the chain interface', () => { - expect(api.chain).toBeDefined(); - }); + it('creates an instance with all sections', () => { + const rpc = new Rpc(new MockProvider()); - it('sets up the state interface', () => { - expect(api.state).toBeDefined(); + expect( + Object.keys(rpc) + ).toEqual([ + 'provider', + 'author', 'chain', 'state', 'system' + ]); }); }); diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index e27194bdbc..785ce76a6c 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -2,12 +2,14 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ProviderInterface, ProviderInterface$Callback } from '@polkadot/rpc-provider/types'; +import { ProviderInterface } from '@polkadot/rpc-provider/types'; import { RpcSection, RpcMethod } from '@polkadot/jsonrpc/types'; import { RpcInterface, RpcInterface$Method, RpcInterface$Section } from './types'; +import memoize from 'memoizee'; +import { combineLatest, from, Observable, Observer, of, throwError } from 'rxjs'; +import { catchError, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import interfaces from '@polkadot/jsonrpc'; -import { WsProvider } from '@polkadot/rpc-provider'; import { Codec } from '@polkadot/types/types'; import { Option, StorageChangeSet, StorageKey, Vector, createClass, createType } from '@polkadot/types'; import { ExtError, assert, isFunction, isNull, logger } from '@polkadot/util'; @@ -39,15 +41,15 @@ const EMPTY_META = { *
* * ```javascript - * import Api from '@polkadot/rpc-core'; + * import Rpc from '@polkadot/rpc-core'; * import WsProvider from '@polkadot/rpc-provider/ws'; * * const provider = new WsProvider('ws://127.0.0.1:9944'); - * const api = new Api(provider); + * const rpc = new Rpc(provider); * ``` */ export default class Rpc implements RpcInterface { - readonly _provider: ProviderInterface; + readonly provider: ProviderInterface; readonly author: RpcInterface$Section; readonly chain: RpcInterface$Section; readonly state: RpcInterface$Section; @@ -58,10 +60,10 @@ export default class Rpc implements RpcInterface { * Default constructor for the Api Object * @param {ProviderInterface} provider An API provider using HTTP or WebSocket */ - constructor (provider: ProviderInterface = new WsProvider()) { + constructor (provider: ProviderInterface) { assert(provider && isFunction(provider.send), 'Expected Provider to API create'); - this._provider = provider; + this.provider = provider; this.author = this.createInterface(interfaces.author); this.chain = this.createInterface(interfaces.chain); @@ -96,17 +98,21 @@ export default class Rpc implements RpcInterface { * @description Manually disconnect from the attached provider */ disconnect (): void { - this._provider.disconnect(); + this.provider.disconnect(); + } + + private createErrorMessage (method: RpcMethod, error: Error) { + return `${Rpc.signature(method)}:: ${error.message}`; } private createInterface ({ methods }: RpcSection): RpcInterface$Section { return Object .keys(methods) - .reduce((exposed, method) => { + .reduce((exposed, methodName) => { - const def = methods[method]; + const def = methods[methodName]; - exposed[method] = def.isSubscription + exposed[methodName] = def.isSubscription ? this.createMethodSubscribe(def) : this.createMethodSend(def); @@ -117,23 +123,40 @@ export default class Rpc implements RpcInterface { private createMethodSend (method: RpcMethod): RpcInterface$Method { const rpcName = `${method.section}_${method.method}`; - const call = async (...values: Array): Promise => { + const call = (...values: Array): Observable => { // TODO Warn on deprecated methods - try { - const params = this.formatInputs(method, values); - const paramsJson = params.map((param) => param.toJSON()); - const result = await this._provider.send(rpcName, paramsJson); - return this.formatOutput(method, params, result); - } catch (error) { - const message = `${Rpc.signature(method)}:: ${error.message}`; + // Here, logically, it should be `of(this.formatInputs(method, values))`. + // However, formatInputs can throw, and when it does, the above way + // doesn't throw in the "Observable loop" (which is internally wrapped in + // a try/catch block). So we: + // - first do `of(1)` - won't throw + // - then do `map(()=>this.formatInputs)` - might throw, but inside Observable. + return of(1) + .pipe( + map(() => this.formatInputs(method, values)), + switchMap((params) => + combineLatest([ + of(params), + from(this.provider.send(rpcName, params.map((param) => param.toJSON()))) + ]) + ), + map(([params, result]) => this.formatOutput(method, params, result)), + catchError((error) => { + const message = this.createErrorMessage(method, error); - l.error(message); + l.error(message); - throw new ExtError(message, (error as ExtError).code, undefined); - } + return throwError(new ExtError(message, (error as ExtError).code, undefined)); + }), + publishReplay(1), // create a Replay(1) + refCount() // Unsubcribe WS when there are no more subscribers + ); }; + // We voluntarily don't cache the "one-shot" RPC calls. For example, + // `getStorage('123')` returns the current value, but this value can change + // over time, so we wouldn't want to cache the Observable. return call as RpcInterface$Method; } @@ -143,40 +166,68 @@ export default class Rpc implements RpcInterface { const unsubName = `${method.section}_${unsubMethod}`; const subType = `${method.section}_${updateType}`; - const unsubscribe = (subscriptionId: any): Promise => - this._provider.unsubscribe(subType, unsubName, subscriptionId); - const _call = async (...values: Array): Promise => { - try { - const cb: ProviderInterface$Callback = values.pop(); + const call = (...values: Array): Observable => { + return new Observable((observer: Observer) => { + let subscriptionPromise: Promise; - assert(isFunction(cb), `Expected callback in last position of params`); + try { + const params = this.formatInputs(method, values); + const paramsJson = params.map((param) => param.toJSON()); + const update = (error?: Error, result?: any) => { + if (error) { + l.error(this.createErrorMessage(method, error)); + return; + } - const params = this.formatInputs(method, values); - const paramsJson = params.map((param) => param.toJSON()); - const update = (error: Error | null, result?: any) => { - if (error) { - l.error(`${Rpc.signature(method)}:: ${error.message}`); - return; - } + observer.next(this.formatOutput(method, params, result)); + }; - cb(this.formatOutput(method, params, result)); + subscriptionPromise = this.provider.subscribe(subType, subName, paramsJson, update); + } catch (error) { + const message = this.createErrorMessage(method, error); + + l.error(message); + + observer.error(new ExtError(message, (error as ExtError).code, undefined)); + } + + // Teardown logic + return () => { + // Delete from cache + // Reason: + // ``` + // const s = api.query.system.accountNonce(addr1).subscribe(); // let's say it's 6 + // s.unsubscribe(); + // // wait a bit, for the nonce to increase to 7 + // api.query.system.accountNonce(addr1).subscribe(); // will output 6 instead of 7 if we don't clear cache + // // that's because all our observables are replay(1) + // ``` + memoized.delete(...values); + // Unsubscribe from provider + subscriptionPromise + .then((subscriptionId) => this.provider.unsubscribe(subType, unsubName, subscriptionId)) + .catch((error: Error) => { + const message = this.createErrorMessage(method, error); + + l.error(message); + }); }; - - return this._provider.subscribe(subType, subName, paramsJson, update); - } catch (error) { - const message = `${Rpc.signature(method)}:: ${error.message}`; - - l.error(message); - - throw new ExtError(message, (error as ExtError).code, undefined); - } + }).pipe( + publishReplay(1), + refCount() + ); }; - const call = _call as RpcInterface$Method; + const memoized = memoize(call, { + // Dynamic length for argument + length: false, + // Normalize args so that different args that should be cached + // together are cached together. + // E.g.: `query.my.method('abc') === query.my.method(new AccountId('abc'));` + normalizer: JSON.stringify + }); - call.unsubscribe = unsubscribe; - - return call; + return memoized; } private formatInputs (method: RpcMethod, inputs: Array): Array { diff --git a/packages/rpc-core/src/methodSend.spec.ts b/packages/rpc-core/src/methodSend.spec.ts index a7daaa707c..15af2be7f4 100644 --- a/packages/rpc-core/src/methodSend.spec.ts +++ b/packages/rpc-core/src/methodSend.spec.ts @@ -2,10 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import Api from '.'; +import Rpc from '.'; describe('methodSend', () => { - let api: Api; + let rpc: Rpc; let methods: any; let provider: any; @@ -33,34 +33,42 @@ describe('methodSend', () => { }) }; - api = new Api(provider); + rpc = new Rpc(provider); }); - it('wraps errors with the call signature', () => { + it('wraps errors with the call signature', (done) => { // @ts-ignore private method - const method = api.createMethodSend(methods.blah); + const method = rpc.createMethodSend(methods.blah); - return method().catch((error) => { - expect(error.message).toMatch(/blah \(foo: Bytes\): Bytes/); - }); + method().subscribe( + () => { /* noop */ }, + (error) => { + expect(error.message).toMatch(/blah \(foo: Bytes\): Bytes/); + done(); + } + ); }); - it('checks for mismatched parameters', () => { + it('checks for mismatched parameters', (done) => { // @ts-ignore private method - const method = api.createMethodSend(methods.bleh); + const method = rpc.createMethodSend(methods.bleh); - return method(1).catch((error) => { - expect(error.message).toMatch(/parameters, 1 found instead/); - }); + method(1).subscribe( + () => { /* noop */ }, + (error) => { + expect(error.message).toMatch(/parameters, 1 found instead/); + done(); + }); }); - it('calls the provider with the correct parameters', () => { + it('calls the provider with the correct parameters', (done) => { // @ts-ignore private method - const method = api.createMethodSend(methods.blah); + const method = rpc.createMethodSend(methods.blah); // Args are length-prefixed, because it's a Bytes - return method(new Uint8Array([2 << 2, 0x12, 0x34])).then(() => { + method(new Uint8Array([2 << 2, 0x12, 0x34])).subscribe(() => { expect(provider.send).toHaveBeenCalledWith('test_blah', ['0x1234']); + done(); }); }); }); diff --git a/packages/rpc-core/src/replay.spec.ts b/packages/rpc-core/src/replay.spec.ts new file mode 100644 index 0000000000..4640f7911b --- /dev/null +++ b/packages/rpc-core/src/replay.spec.ts @@ -0,0 +1,64 @@ +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { of } from 'rxjs'; +import MockProvider from '@polkadot/rpc-provider/mock'; + +import Rpc from '.'; + +describe('replay', () => { + let rpc: Rpc; + + beforeEach(() => { + rpc = new Rpc(new MockProvider()); + }); + + it('subscribes via the rpc section', (done) => { + rpc.chain.getBlockHash = jest.fn(() => of(1)); + + rpc.chain.getBlockHash(123, false).subscribe(() => { + expect( + rpc.chain.getBlockHash + ).toHaveBeenCalledWith(123, false); + + done(); + }); + }); + + it('returns the observable value', (done) => { + rpc.system.chain().subscribe((value) => { + if (value) { + expect(value).toEqual('mockChain'); // Defined in MockProvider + done(); + } + }); + }); + + it('replay(1) works as expected', (done) => { + const observable = rpc.system.chain(); + let a: string | undefined; + observable.subscribe((value) => { a = value; }); + + setTimeout(() => { + // Subscribe again to the same observable, it should fire value immediately + observable.subscribe((value) => { + expect(value).toEqual(a); + done(); + }); + }, 1000); + }); + + it('unsubscribes as required', (done) => { + rpc.provider.unsubscribe = jest.fn(); + + const subscription = rpc.chain.subscribeNewHead().subscribe(() => { + subscription.unsubscribe(); + // There's a promise inside .unsubscribe(), wait a bit + setTimeout(() => { + expect(rpc.provider.unsubscribe).toHaveBeenCalled(); + done(); + }, 200); + }); + }); +}); diff --git a/packages/rpc-core/src/types.ts b/packages/rpc-core/src/types.ts index afdf14ffee..1b5fd9f87a 100644 --- a/packages/rpc-core/src/types.ts +++ b/packages/rpc-core/src/types.ts @@ -2,11 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -export interface RpcInterface$Method { - (...params: Array): Promise; +import { Observable } from 'rxjs'; - subscription: string; - unsubscribe: (id: number) => Promise; +export interface RpcInterface$Method { + (...params: Array): Observable; } export type RpcInterface$Section = { diff --git a/packages/rpc-core/test/e2e/alexander.spec.ts b/packages/rpc-core/test/e2e/alexander.spec.ts new file mode 100644 index 0000000000..b276a6c2ac --- /dev/null +++ b/packages/rpc-core/test/e2e/alexander.spec.ts @@ -0,0 +1,59 @@ +// Copyright 2017-2019 @polkadot/rpc-core authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { BlockNumber, Index, SignedBlock, StorageChangeSet } from '@polkadot/types'; +import storage from '@polkadot/storage/static'; +import WsProvider from '@polkadot/rpc-provider/ws'; + +import Rpc from '../../src'; + +const randomAccount = '5HTqyWJHAVUieZnpb1V8gK4T1E4mnhkrUVSSzWBQd6kYgsVJ'; + +describe.skip('e2e Alexander - Polkadot', () => { + let rpc: Rpc; + + beforeEach(() => { + jest.setTimeout(30000); + rpc = new Rpc(new WsProvider('wss://poc3-rpc.polkadot.io/')); + }); + + it('subscribes to storage', (done) => { + rpc.state + .subscribeStorage([ + [storage.system.accountNonce, randomAccount], + [storage.session.currentIndex] + ]) + .subscribe((data: StorageChangeSet) => { + expect(data).toHaveLength(2); + expect(data).toEqual( + expect.arrayContaining([ + expect.any(BlockNumber), + expect.any(Index) + ]) + ); + + done(); + }); + }); + + it('retrieves a block by hash (krumme lanke #1)', (done) => { + rpc.chain + .getBlock('0x627847bffdf5f3e01ac440d057dec6a37a12a6f329db7ef8367665574b76b5df') + .subscribe((block: SignedBlock) => { + expect(block).toBeDefined(); + expect(block).toBeInstanceOf(SignedBlock); + done(); + }); + }); + + it('retrieves a block by hash (krumme lanke #2)', (done) => { + rpc.chain + .getBlock('0x53416d53a4b1dfcae9165a89d193608e4aa770414f02267f5b2c4015a2e66091') + .subscribe((block: SignedBlock) => { + expect(block).toBeDefined(); + expect(block).toBeInstanceOf(SignedBlock); + done(); + }); + }); +}); diff --git a/packages/rpc-core/test/e2e/alexander.test.ts b/packages/rpc-core/test/e2e/alexander.test.ts deleted file mode 100644 index fff492aa67..0000000000 --- a/packages/rpc-core/test/e2e/alexander.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-core authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { BlockNumber, Index, SignedBlock, StorageChangeSet } from '@polkadot/types'; -import storage from '@polkadot/storage/static'; -import WsProvider from '@polkadot/rpc-provider/ws'; - -import Rpc from '../../src'; - -const randomAccount = '5HTqyWJHAVUieZnpb1V8gK4T1E4mnhkrUVSSzWBQd6kYgsVJ'; - -describe.skip('e2e Alexander - Polkadot', () => { - let api: Rpc; - - beforeEach(() => { - jest.setTimeout(30000); - api = new Rpc(new WsProvider('wss://poc3-rpc.polkadot.io/')); - }); - - it('subscribes to storage', (done) => { - return ( - api.state - .subscribeStorage( - [ - [storage.system.accountNonce, randomAccount], - [storage.session.currentIndex] - ], - (data: StorageChangeSet) => { - expect(data).toHaveLength(2); - expect(data).toEqual( - expect.arrayContaining([ - expect.any(BlockNumber), - expect.any(Index) - ]) - ); - - done(); - }).then((subscriptionId: number) => { - console.log('stoarge subscriptionId =', subscriptionId); - }) - ); - }); - - it('retrieves a block by hash (krumme lanke #1)', () => { - return api.chain - .getBlock('0x627847bffdf5f3e01ac440d057dec6a37a12a6f329db7ef8367665574b76b5df') - .then((block: SignedBlock) => { - expect(block).toBeDefined(); - }) - .catch((error) => { - console.error(error); - - throw error; - }); - }); - - it('retrieves a block by hash (krumme lanke #2)', () => { - return api.chain - .getBlock('0x53416d53a4b1dfcae9165a89d193608e4aa770414f02267f5b2c4015a2e66091') - .then((block: SignedBlock) => { - expect(block).toBeDefined(); - }) - .catch((error) => { - console.error(error); - - throw error; - }); - }); -}); diff --git a/packages/rpc-core/test/e2e/basics.test.ts b/packages/rpc-core/test/e2e/basics.spec.ts similarity index 51% rename from packages/rpc-core/test/e2e/basics.test.ts rename to packages/rpc-core/test/e2e/basics.spec.ts index 66eaf629f0..19a84ed014 100644 --- a/packages/rpc-core/test/e2e/basics.test.ts +++ b/packages/rpc-core/test/e2e/basics.spec.ts @@ -8,36 +8,29 @@ import WsProvider from '@polkadot/rpc-provider/ws'; import Rpc from '../../src'; describe.skip('e2e basics', () => { - let api: Rpc; + let rpc: Rpc; beforeEach(() => { jest.setTimeout(30000); - api = new Rpc(new WsProvider('ws://127.0.0.1:9944')); + rpc = new Rpc(new WsProvider('ws://127.0.0.1:9944')); }); - it('retrieves the pending extrinsics', () => { - return api.author + it('retrieves the pending extrinsics', (done) => { + rpc.author .pendingExtrinsics() - .then((extrinsics: PendingExtrinsics) => { - console.log('extrinsics', extrinsics); - }) - .catch((error) => { - console.error(error); - - throw error; + .subscribe((extrinsics: PendingExtrinsics) => { + expect(extrinsics).toBeInstanceOf(PendingExtrinsics); + done(); }); }); - it('retrieves the system properties', () => { - return api.system + it('retrieves the system properties', (done) => { + rpc.system .properties() - .then((properties: ChainProperties) => { + .subscribe((properties: ChainProperties) => { + expect(properties).toBeInstanceOf(ChainProperties); console.log('properties', properties); - }) - .catch((error) => { - console.error(error); - - throw error; + done(); }); }); }); diff --git a/packages/rpc-core/test/e2e/chain.test.ts b/packages/rpc-core/test/e2e/chain.spec.ts similarity index 52% rename from packages/rpc-core/test/e2e/chain.test.ts rename to packages/rpc-core/test/e2e/chain.spec.ts index 8b65e49bc0..7f1e7f4651 100644 --- a/packages/rpc-core/test/e2e/chain.test.ts +++ b/packages/rpc-core/test/e2e/chain.spec.ts @@ -8,40 +8,33 @@ import WsProvider from '@polkadot/rpc-provider/ws'; import Rpc from '../../src'; describe.skip('e2e chain', () => { - let api: Rpc; + let rpc: Rpc; beforeEach(() => { jest.setTimeout(30000); - api = new Rpc(new WsProvider('ws://127.0.0.1:9944')); + rpc = new Rpc(new WsProvider('ws://127.0.0.1:9944')); }); it('subscribes via subscribeNewHead', (done) => { let count: number = 0; - // tslint:disable-next-line - api.chain - .subscribeNewHead((header: Header) => { - expect(header).toBeDefined(); + rpc.chain + .subscribeNewHead() + .subscribe((header: Header) => { + expect(header).toBeInstanceOf(Header); if (++count === 3) { done(); } - }) - .then((subscriptionId: number) => { - console.log('newHead: subscriptionId =', subscriptionId); }); }); - it('retrieves the runtime version', () => { - return api.chain + it('retrieves the runtime version', (done) => { + rpc.chain .getRuntimeVersion() - .then((version: RuntimeVersion) => { - console.error('version', version); - }) - .catch((error) => { - console.error(error); - - throw error; + .subscribe((version: RuntimeVersion) => { + expect(version).toBeInstanceOf(RuntimeVersion); + done(); }); }); }); diff --git a/packages/rpc-core/test/e2e/state.test.ts b/packages/rpc-core/test/e2e/state.spec.ts similarity index 67% rename from packages/rpc-core/test/e2e/state.test.ts rename to packages/rpc-core/test/e2e/state.spec.ts index cc3fa15611..a7885fca5a 100644 --- a/packages/rpc-core/test/e2e/state.test.ts +++ b/packages/rpc-core/test/e2e/state.spec.ts @@ -22,81 +22,66 @@ const CODE = '0x3a636f6465'; // :code const CHILD_STORAGE = '0x3a6368696c645f73746f726167653a'; // :child_storage: describe.skip('e2e state', () => { - let api: Rpc; + let rpc: Rpc; beforeEach(() => { jest.setTimeout(30000); - api = new Rpc(new WsProvider('ws://127.0.0.1:9944')); + rpc = new Rpc(new WsProvider('ws://127.0.0.1:9944')); }); - it('getMetadata(): retrieves the wasm metadata', () => { - return api.state + it('getMetadata(): retrieves the wasm metadata', (done) => { + rpc.state .getMetadata() - .then((meta: Metadata) => { + .subscribe((meta: Metadata) => { console.error(JSON.stringify(meta.toJSON())); - }) - .catch((error) => { - console.error(error); - - throw error; + done(); }); }); - it('getKeys(): retrieves storage keys for ":code"', () => { - return api.state + it('getKeys(): retrieves storage keys for ":code"', (done) => { + rpc.state .getKeys(CODE) - .then((keys: Array) => { + .subscribe((keys: Array) => { expect(keys.length).toEqual(1); + done(); }); }); describe('test-suite getStorage()', () => { - it('retrieves code', () => { - return api.state + it('retrieves code', (done) => { + rpc.state .getStorage([ storage.substrate.code ]) - .then((code: Bytes) => { + .subscribe((code: Bytes) => { console.error(code.toHex().substr(0, 256), '...'); - }) - .catch((error) => { - console.error(error); - - throw error; + done(); }); }); - it('retrieves balances', () => { - return api.state + it('retrieves balances', (done) => { + rpc.state .getStorage([ storage.balances.freeBalance, ALICE ]) - .then((balance: Balance) => { + .subscribe((balance: Balance) => { console.error(balance); expect(balance.isZero()).not.toEqual(true); - }) - .catch((error) => { - console.error(error); - - throw error; + done(); }); }); - it('retrieves timestamp', () => { - return api.state + it('retrieves timestamp', (done) => { + rpc.state .getStorage([ storage.timestamp.now ]) - .then((moment: Moment) => { + .subscribe((moment: Moment) => { console.error(moment); expect(moment.toNumber()).not.toEqual(0); - }) - .catch((error) => { - console.error(error); - - throw error; + done(); }); }); }); @@ -134,46 +119,50 @@ describe.skip('e2e state', () => { }); }); - it('getChildKeys(): retrieves :child_storage: keys for one deployed flipper contract', async () => { - const storageKeys = await api.state.getKeys(CHILD_STORAGE); + it('getChildKeys(): retrieves :child_storage: keys for one deployed flipper contract', async (done) => { + const storageKeys = await rpc.state.getKeys(CHILD_STORAGE).toPromise(); - return api.state + rpc.state .getChildKeys(storageKeys[0], '0x') - .then((keys: Array) => { + .subscribe((keys: Array) => { expect(keys.length).toBeGreaterThanOrEqual(1); + done(); }); }); - it('getChildStorage(): retrieves the default value of the flipper smart contract', async () => { - const storageKeys = await api.state.getKeys(CHILD_STORAGE); - const childStorageKeys = await api.state.getChildKeys(storageKeys[0], '0x'); + it('getChildStorage(): retrieves the default value of the flipper smart contract', async (done) => { + const storageKeys = await rpc.state.getKeys(CHILD_STORAGE).toPromise(); + const childStorageKeys = await rpc.state.getChildKeys(storageKeys[0], '0x').toPromise(); - return api.state + rpc.state .getChildStorage(storageKeys[0], childStorageKeys[0]) - .then((storage: StorageData) => { + .subscribe((storage: StorageData) => { expect(storage.toString()).toBe('0x00'); + done(); }); }); - it('getChildStorageHash(): retrieves the Hash of the flipper smart contract', async () => { - const storageKeys = await api.state.getKeys(CHILD_STORAGE); - const childStorageKeys = await api.state.getChildKeys(storageKeys[0], '0x'); + it('getChildStorageHash(): retrieves the Hash of the flipper smart contract', async (done) => { + const storageKeys = await rpc.state.getKeys(CHILD_STORAGE).toPromise(); + const childStorageKeys = await rpc.state.getChildKeys(storageKeys[0], '0x').toPromise(); - return api.state + rpc.state .getChildStorageHash(storageKeys[0], childStorageKeys[0]) - .then((storage: StorageData) => { + .subscribe((storage: StorageData) => { expect(storage.toString()).toBe('0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314'); + done(); }); }); - it('getChildStorageSize(): retrieves the size of the flipper smart contract', async () => { - const storageKeys = await api.state.getKeys(CHILD_STORAGE); - const childStorageKeys = await api.state.getChildKeys(storageKeys[0], '0x'); + it('getChildStorageSize(): retrieves the size of the flipper smart contract', async (done) => { + const storageKeys = await rpc.state.getKeys(CHILD_STORAGE).toPromise(); + const childStorageKeys = await rpc.state.getChildKeys(storageKeys[0], '0x').toPromise(); - return api.state + rpc.state .getChildStorageSize(storageKeys[0], childStorageKeys[0]) - .then((storage: StorageData) => { + .subscribe((storage: StorageData) => { expect(storage.toString()).toBe('1'); + done(); }); }); }); diff --git a/packages/rpc-core/test/e2e/subscribe.test.ts b/packages/rpc-core/test/e2e/subscribe.spec.ts similarity index 69% rename from packages/rpc-core/test/e2e/subscribe.test.ts rename to packages/rpc-core/test/e2e/subscribe.spec.ts index 07e216a678..281b18d800 100644 --- a/packages/rpc-core/test/e2e/subscribe.test.ts +++ b/packages/rpc-core/test/e2e/subscribe.spec.ts @@ -9,26 +9,24 @@ import storage from '@polkadot/storage/static'; import Rpc from '../../src'; describe.skip('e2e subscriptions', () => { - let api: Rpc; + let rpc: Rpc; beforeEach(() => { jest.setTimeout(30000); - api = new Rpc(new WsProvider('ws://127.0.0.1:9944')); + rpc = new Rpc(new WsProvider('ws://127.0.0.1:9944')); }); it('retrieves current timestamp', (done) => { let count: number = 0; - return api.state - .subscribeStorage([[storage.timestamp.now]], (data: StorageChangeSet) => { + rpc.state + .subscribeStorage([[storage.timestamp.now]]) + .subscribe((data: StorageChangeSet) => { expect(data).toBeDefined(); if (++count === 3) { done(); } - }) - .then((subscriptionId: number) => { - console.log('newHead: subscriptionId =', subscriptionId); }); }); }); diff --git a/packages/rpc-rx/LICENSE b/packages/rpc-rx/LICENSE deleted file mode 100644 index 0d381b2e97..0000000000 --- a/packages/rpc-rx/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/rpc-rx/README.md b/packages/rpc-rx/README.md deleted file mode 100644 index 23ed7c4d4c..0000000000 --- a/packages/rpc-rx/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# @polkadot/rpc-rx - -An RxJs wrapper around the [@polkadot/rpc-core](../rpc-core). - -## Classes - -[Classes](SUMMARY.md) diff --git a/packages/rpc-rx/package.json b/packages/rpc-rx/package.json deleted file mode 100644 index 3a8c4b7c1e..0000000000 --- a/packages/rpc-rx/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@polkadot/rpc-rx", - "version": "0.82.0-beta.13", - "description": "An RxJs wrapper around the Polkadot JS API", - "main": "index.js", - "keywords": [ - "Polkadot", - "RxJs" - ], - "author": "Jaco Greeff ", - "maintainers": [ - "Jaco Greeff " - ], - "contributors": [], - "license": "Apache-2.0", - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/polkadot-js/api.git" - }, - "bugs": { - "url": "https://github.com/polkadot-js/api/issues" - }, - "homepage": "https://github.com/polkadot-js/api/tree/master/packages/rpc-rx#readme", - "dependencies": { - "@babel/runtime": "^7.4.5", - "@polkadot/rpc-core": "^0.82.0-beta.13", - "@polkadot/rpc-provider": "^0.82.0-beta.13", - "@types/memoizee": "^0.4.2", - "@types/rx": "^4.1.1", - "memoizee": "^0.4.14", - "rxjs": "^6.5.2" - }, - "devDependencies": { - "@polkadot/keyring": "^0.94.0-beta.3" - } -} diff --git a/packages/rpc-rx/src/cached.spec.ts b/packages/rpc-rx/src/cached.spec.ts deleted file mode 100644 index 9ccaa8df95..0000000000 --- a/packages/rpc-rx/src/cached.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { Observable } from 'rxjs'; - -import testingPairs from '@polkadot/keyring/testingPairs'; -import { RpcInterface$Section } from '@polkadot/rpc-core/types'; -import { AccountId } from '@polkadot/types'; - -jest.mock('@polkadot/rpc-provider/ws', () => class { - isConnected = () => true; - on = () => true; - send = () => true; -}); - -import RpcRx from '.'; - -describe('createCachedObservable', () => { - let api: RpcRx; - let creator: (...params: Array) => Observable; - const keyring = testingPairs(); - let section: RpcInterface$Section; - - beforeEach(() => { - api = new RpcRx(); - }); - - beforeEach(() => { - // Create two methods in our section - const subMethod: any = jest.fn(() => Promise.resolve('subMethodResult')); - subMethod.unsubscribe = jest.fn(() => Promise.resolve(true)); - const subMethod2: any = jest.fn(() => Promise.resolve('subMethod2Result')); - subMethod2.unsubscribe = jest.fn(() => Promise.resolve(true)); - - section = { - subMethod, - subMethod2 - }; - - // @ts-ignore Private method - creator = api.createObservable('subMethod', section); - }); - - it('creates a single observable', () => { - creator(123).subscribe(); - - expect( - section.subMethod - ).toHaveBeenCalledWith(123, expect.anything()); - }); - - it('creates a single observable (multiple calls)', () => { - const observable1 = creator(123); - const observable2 = creator(123); - - expect( - observable2 - ).toBe(observable1); - }); - - it('creates a single observable (multiple calls, different arguments that should be cached together)', () => { - const observable1 = creator(keyring.alice.address); - const observable2 = creator(new AccountId(keyring.alice.address)); - - expect( - observable2 - ).toBe(observable1); - }); - - it('creates multiple observables for different values', () => { - const observable1 = creator(123); - const observable2 = creator(456); - - expect( - observable2 - ).not.toBe(observable1); - }); - - it('creates different observables for different methods but same arguments', () => { - const observable1 = creator(123); - // @ts-ignore Private method - const observable2 = api.createObservable('subMethod2', section)(123); - - expect( - observable2 - ).not.toBe(observable1); - }); -}); diff --git a/packages/rpc-rx/src/index.spec.ts b/packages/rpc-rx/src/index.spec.ts deleted file mode 100644 index cc632d86ec..0000000000 --- a/packages/rpc-rx/src/index.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { isFunction } from '@polkadot/util'; - -jest.mock('@polkadot/rpc-provider/ws', () => class { - isConnected = () => true; - on = () => true; - send = () => true; -}); - -import RpcRx from '.'; - -describe('RpcRx', () => { - let api: RpcRx; - - beforeEach(() => { - api = new RpcRx(); - }); - - 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); - }); -}); diff --git a/packages/rpc-rx/src/index.ts b/packages/rpc-rx/src/index.ts deleted file mode 100644 index 9fd5e3fa9e..0000000000 --- a/packages/rpc-rx/src/index.ts +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { RpcInterface$Method, RpcInterface$Section } from '@polkadot/rpc-core/types'; -import { ProviderInterface } from '@polkadot/rpc-provider/types'; -import { RpcRxInterface, RpcRxInterface$Events, RpcRxInterface$Method, RpcRxInterface$Section } from './types'; - -import EventEmitter from 'eventemitter3'; -import memoize, { Memoized } from 'memoizee'; -import { BehaviorSubject, Observable, from, Observer } from 'rxjs'; -import Rpc from '@polkadot/rpc-core'; -import { map, publishReplay, refCount } from 'rxjs/operators'; -import { isFunction, isUndefined } from '@polkadot/util'; - -/** - * @name RpcRx - * @summary The RxJS API is a wrapper around the API. - * @description It allows wrapping API components with observables using RxJS. - * - * @example - *
- * - * ```javascript - * import RpcRx from '@polkadot/rpc-rx'; - * import WsProvider from '@polkadot/rpc-provider/ws'; - * - * const provider = new WsProvider('http://127.0.0.1:9944'); - * const api = new RpcRx(provider); - * ``` - */ -export default class RpcRx implements RpcRxInterface { - private _api: Rpc; - private _eventemitter: EventEmitter; - private _isConnected: BehaviorSubject; - readonly author: RpcRxInterface$Section; - readonly chain: RpcRxInterface$Section; - readonly state: RpcRxInterface$Section; - readonly system: RpcRxInterface$Section; - - /** - * @param {ProviderInterface} provider An API provider using HTTP or WebSocket - */ - constructor (providerOrRpc?: Rpc | ProviderInterface) { - this._api = providerOrRpc instanceof Rpc - ? providerOrRpc - : new Rpc(providerOrRpc); - this._eventemitter = new EventEmitter(); - this._isConnected = new BehaviorSubject(this._api._provider.isConnected()); - - this.initEmitters(this._api._provider); - - this.author = this.createInterface(this._api.author); - this.chain = this.createInterface(this._api.chain); - this.state = this.createInterface(this._api.state); - this.system = this.createInterface(this._api.system); - } - - isConnected (): BehaviorSubject { - return this._isConnected; - } - - on (type: RpcRxInterface$Events, handler: (...args: Array) => any): void { - this._eventemitter.on(type, handler); - } - - protected emit (type: RpcRxInterface$Events, ...args: Array): void { - this._eventemitter.emit(type, ...args); - } - - private initEmitters (provider: ProviderInterface): void { - provider.on('connected', () => { - this._isConnected.next(true); - - this.emit('connected'); - }); - - provider.on('disconnected', () => { - this._isConnected.next(false); - - this.emit('disconnected'); - }); - } - - private createInterface (section: RpcInterface$Section): RpcRxInterface$Section { - return Object - .keys(section) - .filter((name) => !['subscribe', 'unsubscribe'].includes(name)) - .reduce((observables, name) => { - observables[name] = this.createObservable(name, section); - - return observables; - }, ({} as RpcRxInterface$Section)); - } - - private createObservable (name: string, section: RpcInterface$Section): RpcRxInterface$Method { - if (isFunction(section[name].unsubscribe)) { - const memoized: Memoized = memoize( - (...params: Array) => this.createReplay(name, params, section, memoized), - { - length: false, - // Normalize args so that different args that should be cached - // together are cached together. - // E.g.: `query.my.method('abc') === query.my.method(new AccountId('abc'));` - normalizer: (args) => { - // `args` is arguments object as accessible in memoized function - return JSON.stringify(args); - } - } - ); - - return memoized as unknown as RpcRxInterface$Method; - } - - // We voluntarily don't cache the "one-shot" RPC calls. For example, - // `getStorage('123')` returns the current value, but this value can change - // over time, so we wouldn't want to cache the Observable. - return (...params: Array): Observable => - from( - section[name] - .apply(null, params) - .catch((error: Error) => { - console.error(error); - }) - ); - } - - private createReplay (name: string, params: Array, section: RpcInterface$Section, memoized: Memoized): Observable { - return new Observable((observer: Observer): Function => { - const fn = section[name]; - const callback = this.createReplayCallback(observer); - const subscribe = fn(...params, callback).catch((error) => - observer.next(error) - ); - - return this.createReplayUnsub(fn, subscribe, params, memoized); - }) - .pipe( - map((value) => { - if (value instanceof Error) { - throw value; - } - - return value; - }), - publishReplay(1), - refCount() - ); - } - - private createReplayCallback (observer: Observer) { - let cachedResult: any; - - return (result: any) => { - if (isUndefined(cachedResult) || !Array.isArray(cachedResult) || !Array.isArray(result) - || result.length !== cachedResult.length) { - cachedResult = result; - } else { - cachedResult = cachedResult.map((cachedValue, index) => - isUndefined(result[index]) - ? cachedValue - : result[index] - ); - } - - observer.next(cachedResult); - }; - } - - private createReplayUnsub (fn: RpcInterface$Method, subscribe: Promise, params: Array, memoized: Memoized): () => void { - return (): void => { - subscribe - .then((subscriptionId: number) => - fn.unsubscribe(subscriptionId) - ) - .then(() => { - memoized.delete(...params); - }) - .catch((error) => { - console.error('Unsubscribe failed', error); - }); - }; - } -} diff --git a/packages/rpc-rx/src/replay.spec.ts b/packages/rpc-rx/src/replay.spec.ts deleted file mode 100644 index 2567dad81c..0000000000 --- a/packages/rpc-rx/src/replay.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { Observable } from 'rxjs'; - -import { RpcInterface$Section } from '@polkadot/rpc-core/types'; - -jest.mock('@polkadot/rpc-provider/ws', () => class { - isConnected = () => true; - on = () => true; - send = () => true; -}); - -import RpcRx from '.'; - -describe('replay', () => { - const params = [123, false]; - let api: RpcRx; - let section: RpcInterface$Section; - let observable: Observable; - let update: any; - - beforeEach(() => { - api = new RpcRx(); - }); - - beforeEach(() => { - const subMethod: any = jest.fn((name, ...params) => { - update = params.pop(); - - return Promise.resolve(12345); - }); - - subMethod.unsubscribe = jest.fn(() => { - return Promise.resolve(true); - }); - - section = { - subMethod - }; - - // @ts-ignore - observable = api.createObservable('subMethod', section)(...params); - }); - - it('subscribes via the api section', (done) => { - observable.subscribe((value) => { - if (value) { - expect( - section.subMethod - ).toHaveBeenCalledWith(123, false, expect.anything()); - - done(); - } - }); - - update('test'); - }); - - it('returns the observable value', (done) => { - observable.subscribe((value) => { - if (value) { - expect(value).toEqual('test'); - done(); - } - }); - - update('test'); - }); - - it('unsubscribes as required', (done) => { - section.subMethod.unsubscribe = async () => { - done(); - }; - - let subscription = observable.subscribe((value) => { - if (value) { - subscription.unsubscribe(); - } - }); - - update('test'); - }); -}); diff --git a/packages/rpc-rx/src/types.ts b/packages/rpc-rx/src/types.ts deleted file mode 100644 index b0e93399f0..0000000000 --- a/packages/rpc-rx/src/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import { Observable } from 'rxjs'; -import { ProviderInterface$Emitted } from '@polkadot/rpc-provider/types'; - -export type RpcRxInterface$Method = (...params: Array) => Observable; - -export type RpcRxInterface$Section = { - [index: string]: RpcRxInterface$Method -}; - -export type RpcRxInterface$Events = ProviderInterface$Emitted; - -export type RpcRxInterface = { - readonly author: RpcRxInterface$Section; - readonly chain: RpcRxInterface$Section; - readonly state: RpcRxInterface$Section; - readonly system: RpcRxInterface$Section; - - isConnected: () => Observable - on: (type: RpcRxInterface$Events, handler: (...args: Array) => any) => void; -}; diff --git a/packages/rpc-rx/test/e2e/subscribe.test.js b/packages/rpc-rx/test/e2e/subscribe.test.js deleted file mode 100644 index 6d438fdb2d..0000000000 --- a/packages/rpc-rx/test/e2e/subscribe.test.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017-2019 @polkadot/rpc-rx authors & contributors -// This software may be modified and distributed under the terms -// of the Apache-2.0 license. See the LICENSE file for details. - -import WsProvider from '@polkadot/rpc-provider/ws'; -import storage from '@polkadot/storage/static'; - -import RpcRx from '../../src'; - -describe.skip('e2e subscriptions', () => { - let api; - - beforeEach(() => { - jest.setTimeout(30000); - - api = new RpcRx(new WsProvider('ws://127.0.0.1:9944')); - }); - - it('retrieves current timestamp', (done) => { - let count = 0; - - api.state - .subscribeStorage([[storage.timestamp.now]]) - .subscribe((data) => { - console.error('timestamp.now', data); - - if (++count === 4) { - done(); - } - }); - }); - - it('retrieves multiple values', (done) => { - api.state - .subscribeStorage([ - [storage.timestamp.now], - [storage.session.sessionLength], - [storage.staking.sessionsPerEra] - ]) - .subscribe((data) => { - console.error('multiples', data); - - if (data !== undefined && data.filter((item) => item).length === 3) { - done(); - } - }); - }); -}); diff --git a/packages/type-extrinsics/test/e2e/transfer.spec.ts b/packages/type-extrinsics/test/e2e/transfer.spec.ts index 769136165a..3420200316 100644 --- a/packages/type-extrinsics/test/e2e/transfer.spec.ts +++ b/packages/type-extrinsics/test/e2e/transfer.spec.ts @@ -26,31 +26,31 @@ describe.skip('e2e transfer', () => { }); // Error: [1002]: Inherent transactions cannot be queued. - it('inherent test', () => { + it('inherent test', (done) => { const inherent = extrinsics.timestamp.set(Math.round(Date.now() / 1000)); - return api.author.submitExtrinsic(inherent.toU8a()).then(console.log); + api.author.submitExtrinsic(inherent.toU8a()).subscribe(done); }); - it('makes a transfer for a transaction', () => { - return api.chain + it('makes a transfer for a transaction', (done) => { + api.chain .getBlockHash(0) - .then((genesisHash) => { + .subscribe((genesisHash) => { const extrinsic = extrinsics.balances.transfer(keyring.bob.publicKey, 6969) as any; extrinsic.sign(keyring.alice, { blockHash: genesisHash, nonce: 0 }); - return api.author.submitExtrinsic(extrinsic.toU8a()); + api.author.submitExtrinsic(extrinsic.toU8a()).subscribe(done); }); }); it('makes a transfer via watch', (done) => { - return api.chain + api.chain .getBlockHash(0) - .then((genesisHash) => { + .subscribe((genesisHash) => { const extrinsic = extrinsics.balances.transfer(keyring.bob.publicKey, 6969) as any; extrinsic.sign(keyring.alice, { blockHash: genesisHash, nonce: 0 }); - return api.author.submitAndWatchExtrinsic(extrinsic, (result: SubmittableResult) => { + api.author.submitAndWatchExtrinsic(extrinsic).subscribe((result: SubmittableResult) => { if (result.status.isFinalized) { done(); } diff --git a/tsconfig.json b/tsconfig.json index 4caefbbd9a..8b4cbbed77 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,6 @@ "@polkadot/rpc-core/*": ["packages/rpc-core/src/*"], "@polkadot/rpc-provider": ["packages/rpc-provider/src"], "@polkadot/rpc-provider/*": ["packages/rpc-provider/src/*"], - "@polkadot/rpc-rx": ["packages/rpc-rx/src"], - "@polkadot/rpc-rx/*": ["packages/rpc-rx/src/*"], "@polkadot/extrinsics": ["packages/type-extrinsics/src"], "@polkadot/extrinsics/*": ["packages/type-extrinsics/src/*"], "@polkadot/jsonrpc": ["packages/type-jsonrpc/src"], diff --git a/yarn.lock b/yarn.lock index ab89219323..81ebeb37be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2051,107 +2051,6 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== -"@types/rx-core-binding@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3" - integrity sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ== - dependencies: - "@types/rx-core" "*" - -"@types/rx-core@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60" - integrity sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA= - -"@types/rx-lite-aggregates@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2" - integrity sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-async@*": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c" - integrity sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-backpressure@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56" - integrity sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-coincidence@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0" - integrity sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-experimental@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd" - integrity sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0= - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-joinpatterns@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e" - integrity sha1-9w/jcFGKhDLykVjMkv+1a05K/D4= - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-testing@*": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9" - integrity sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek= - dependencies: - "@types/rx-lite-virtualtime" "*" - -"@types/rx-lite-time@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4" - integrity sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite-virtualtime@*": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537" - integrity sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg== - dependencies: - "@types/rx-lite" "*" - -"@types/rx-lite@*": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253" - integrity sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w== - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - -"@types/rx@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48" - integrity sha1-WY/JSla67ZdfGUV04PVy/Y5iekg= - dependencies: - "@types/rx-core" "*" - "@types/rx-core-binding" "*" - "@types/rx-lite" "*" - "@types/rx-lite-aggregates" "*" - "@types/rx-lite-async" "*" - "@types/rx-lite-backpressure" "*" - "@types/rx-lite-coincidence" "*" - "@types/rx-lite-experimental" "*" - "@types/rx-lite-joinpatterns" "*" - "@types/rx-lite-testing" "*" - "@types/rx-lite-time" "*" - "@types/rx-lite-virtualtime" "*" - "@types/secp256k1@^3.5.0": version "3.5.0" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-3.5.0.tgz#0f3baf16b07488c6da2633a63b4160bcf8d0fd5b"