Do not use subscriptions on single subs (#2378)

* Do not use subscriptions on single subs

* Apply suggestions from code review
This commit is contained in:
Jaco Greeff
2020-06-23 09:29:49 +02:00
committed by GitHub
parent 84eb806f07
commit c7315ffb74
5 changed files with 35 additions and 38 deletions
+25 -27
View File
@@ -21,7 +21,7 @@ import { Metadata, Null, Option, Raw, Text, TypeRegistry, u64 } from '@polkadot/
import Linkage, { LinkageResult } from '@polkadot/types/codec/Linkage';
import { DEFAULT_VERSION as EXTRINSIC_DEFAULT_VERSION } from '@polkadot/types/extrinsic/constants';
import StorageKey, { StorageEntry, unwrapStorageType } from '@polkadot/types/primitive/StorageKey';
import { assert, compactStripLength, logger, u8aToHex } from '@polkadot/util';
import { assert, compactStripLength, isFunction, logger, u8aToHex } from '@polkadot/util';
import { createSubmittable } from '../submittable';
import augmentObject from '../util/augmentObject';
@@ -145,7 +145,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
/**
* @description Creates an instance of a type as registered
*/
public createType <K extends keyof InterfaceTypes> (type: K, ...params: any[]): InterfaceTypes[K] {
public createType <K extends keyof InterfaceTypes> (type: K, ...params: unknown[]): InterfaceTypes[K] {
return this.registry.createType(type, ...params);
}
@@ -320,68 +320,50 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
private _decorateStorageEntry<ApiType extends ApiTypes> (creator: StorageEntry, decorateMethod: DecorateMethod<ApiType>): QueryableStorageEntry<ApiType> {
// get the storage arguments, with DoubleMap as an array entry, otherwise spread
const getArgs = (...args: any[]): any[] => extractStorageArgs(creator, args);
const getArgs = (...args: unknown[]): unknown[] => extractStorageArgs(creator, args);
// Disable this where it occurs for each field we are decorating
/* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
// FIXME We probably want to be able to query the full list with non-subs as well
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const decorated = this.hasSubscriptions && creator.iterKey && creator.meta.type.isMap && creator.meta.type.asMap.linked.isTrue
? this._decorateStorageLinked(creator, decorateMethod)
: decorateMethod((...args: any[]): Observable<Codec> => (
this.hasSubscriptions
? this._rpcCore.state
// Unfortunately for one-shot calls we also use .subscribeStorage here
.subscribeStorage<[Codec]>([getArgs(...args)])
// state_storage returns an array of values, since we have just subscribed to
// a single entry, we pull that from the array and return it as-is
.pipe(map(([data]): Codec => data))
: this._rpcCore.state.getStorage(getArgs(...args))
), { methodName: creator.method });
: this._decorateStorageCall(creator, decorateMethod);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
decorated.creator = creator;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.at = decorateMethod((hash: Hash, arg1?: Arg, arg2?: Arg): Observable<Codec> =>
this._rpcCore.state.getStorage(getArgs(arg1, arg2), hash));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.hash = decorateMethod((arg1?: Arg, arg2?: Arg): Observable<Hash> =>
this._rpcCore.state.getStorageHash(getArgs(arg1, arg2)));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
decorated.key = (arg1?: Arg, arg2?: Arg): string =>
u8aToHex(compactStripLength(creator(creator.meta.type.isDoubleMap ? [arg1, arg2] : arg1))[1]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
decorated.keyPrefix = (key1?: Arg): string =>
u8aToHex(creator.keyPrefix(key1));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.range = decorateMethod((range: [Hash, Hash?], arg1?: Arg, arg2?: Arg): Observable<[Hash, Codec][]> =>
this._decorateStorageRange(decorated, [arg1, arg2], range));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.size = decorateMethod((arg1?: Arg, arg2?: Arg): Observable<u64> =>
this._rpcCore.state.getStorageSize(getArgs(arg1, arg2)));
// .keys() & .entries() only available on map types
if (creator.iterKey && (creator.meta.type.isMap || creator.meta.type.isDoubleMap)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.entries = decorateMethod(
memo((doubleMapArg?: Arg): Observable<[StorageKey, Codec][]> =>
this._retrieveMapEntries(creator, doubleMapArg)));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.entriesPaged = decorateMethod(
memo((opts: PaginationOptions): Observable<[StorageKey, Codec][]> =>
this._retrieveMapEntriesPaged(creator, opts)));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.keys = decorateMethod(
memo((doubleMapArg?: Arg): Observable<StorageKey[]> =>
this._retrieveMapKeys(creator, doubleMapArg)));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.keysPaged = decorateMethod(
memo((opts: PaginationOptions): Observable<StorageKey[]> =>
this._retrieveMapKeysPaged(creator, opts)));
@@ -390,15 +372,29 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
// only support multi where subs are available
if (this.hasSubscriptions) {
// When using double map storage function, user need to pass double map key as an array
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
decorated.multi = decorateMethod((args: (Arg | Arg[])[]): Observable<Codec[]> =>
this._rpcCore.state.subscribeStorage(
args.map((arg: Arg[] | Arg): [StorageEntry, Arg | Arg[]] => [creator, arg])));
}
/* eslint-enable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
return this._decorateFunctionMeta(creator as any, decorated) as unknown as QueryableStorageEntry<ApiType>;
}
// Decorate the base storage call. In the case or rxjs or promise-without-callback (await)
// we make a subscription, alternatively we push this through a single-shot query
private _decorateStorageCall<ApiType extends ApiTypes> (creator: StorageEntry, decorateMethod: DecorateMethod<ApiType>): ReturnType<DecorateMethod<ApiType>> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return decorateMethod((...args: unknown[]): Observable<Codec> => {
return this.hasSubscriptions && (this._type === 'rxjs' || (!!args.length && isFunction(args[args.length - 1])))
? this._rpcCore.state.subscribeStorage<[Codec]>([extractStorageArgs(creator, args)]).pipe(
map(([data]) => data) // extract first/only result from list
)
: this._rpcCore.state.getStorage(extractStorageArgs(creator, args));
}, { methodName: creator.method });
}
private _decorateStorageRange<ApiType extends ApiTypes> (decorated: QueryableStorageEntry<ApiType>, args: [Arg?, Arg?], range: [Hash, Hash?]): Observable<[Hash, Codec][]> {
const outputType = unwrapStorageType(decorated.creator.meta.type, decorated.creator.meta.modifier.isOptional);
@@ -412,6 +408,8 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
));
}
// This is deprecated/removed on substrate - we still have some live chains, eg. Edgeware needing it, so it
// has not been dropped yet. Slated for removal in API 2.0
private _decorateStorageLinked<ApiType extends ApiTypes> (creator: StorageEntry, decorateMethod: DecorateMethod<ApiType>): ReturnType<DecorateMethod<ApiType>> {
const result = new Map<Codec, ITuple<[Codec, Linkage<Codec>]> | null>();
let subject: BehaviorSubject<LinkageResult>;
@@ -477,7 +475,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
// 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
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return decorateMethod((...args: any[]): Observable<LinkageResult | [Codec, Linkage<Codec>]> =>
return decorateMethod((...args: unknown[]): Observable<LinkageResult | [Codec, Linkage<Codec>]> =>
args.length
? this._rpcCore.state
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+3 -4
View File
@@ -18,15 +18,14 @@ interface Tracker {
}
// extract the arguments and callback params from a value array possibly containing a callback
function extractArgs (args: any[], needsCallback: boolean): [any[], Callback<Codec> | undefined] {
function extractArgs (args: unknown[], needsCallback: boolean): [unknown[], Callback<Codec> | undefined] {
let callback: Callback<Codec> | undefined;
const actualArgs = args.slice();
// If the last arg is a function, we pop it, put it into callback.
// actualArgs will then hold the actual arguments to be passed to `method`
if (args.length && isFunction(args[args.length - 1])) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
callback = actualArgs.pop();
callback = actualArgs.pop() as Callback<Codec>;
}
// When we need a subscription, ensure that a valid callback is actually passed
@@ -66,7 +65,7 @@ function promiseTracker (resolve: (value: VoidFn) => void, reject: (value: Error
export function decorateMethod<Method extends DecorateFn<ObsInnerType<ReturnType<Method>>>> (method: Method, options?: DecorateMethodOptions): StorageEntryPromiseOverloads {
const needsCallback = options && options.methodName && options.methodName.includes('subscribe');
return function (...args: any[]): Promise<ObsInnerType<ReturnType<Method>>> | UnsubscribePromise {
return function (...args: unknown[]): Promise<ObsInnerType<ReturnType<Method>>> | UnsubscribePromise {
const [actualArgs, resultCb] = extractArgs(args, !!needsCallback);
if (!resultCb) {
+5 -5
View File
@@ -232,8 +232,9 @@ export default class Rpc implements RpcInterface {
const subName = `${section}_${subMethod}`;
const unsubName = `${section}_${unsubMethod}`;
const subType = `${section}_${updateType}`;
let memoized: null | RpcInterfaceMethod & memoizee.Memoized<RpcInterfaceMethod> = null;
const creator = (isRaw: boolean) => (...values: any[]): Observable<any> => {
const creator = (isRaw: boolean) => (...values: unknown[]): Observable<any> => {
return new Observable((observer: Observer<any>): VoidCallback => {
// Have at least an empty promise, as used in the unsubscribe
let subscriptionPromise: Promise<number | void> = Promise.resolve();
@@ -282,8 +283,7 @@ export default class Rpc implements RpcInterface {
// api.query.system.account(addr1).subscribe(); // will output 6 instead of 7 if we don't clear cache
// // that's because all our observables are replay(1)
// ```
// eslint-disable-next-line @typescript-eslint/no-use-before-define
memoized.delete(...values);
memoized && memoized.delete(...values);
// Unsubscribe from provider
subscriptionPromise
@@ -292,12 +292,12 @@ export default class Rpc implements RpcInterface {
? this.provider.unsubscribe(subType, unsubName, subscriptionId)
: Promise.resolve(false)
)
.catch((error: Error): void => logErrorMessage(method, def, error));
.catch((error: Error) => logErrorMessage(method, def, error));
};
}).pipe(drr());
};
const memoized = memoizee(this._createMethodWithRaw(creator), {
memoized = memoizee(this._createMethodWithRaw(creator), {
// Dynamic length for argument
length: false,
// Normalize args so that different args that should be cached
+1 -1
View File
@@ -153,7 +153,7 @@ export class TypeRegistry implements Registry {
/**
* @description Creates an instance of a type as registered
*/
public createType <K extends keyof InterfaceTypes> (type: K, ...params: any[]): InterfaceTypes[K] {
public createType <K extends keyof InterfaceTypes> (type: K, ...params: unknown[]): InterfaceTypes[K] {
return createType(this, type, ...params);
}
+1 -1
View File
@@ -121,7 +121,7 @@ export interface Registry {
findMetaEvent (eventIndex: Uint8Array): Constructor<any>;
createClass <K extends keyof InterfaceTypes> (type: K): Constructor<InterfaceTypes[K]>;
createType <K extends keyof InterfaceTypes> (type: K, ...params: any[]): InterfaceTypes[K];
createType <K extends keyof InterfaceTypes> (type: K, ...params: unknown[]): InterfaceTypes[K];
get <T extends Codec = Codec> (name: string, withUnknown?: boolean): Constructor<T> | undefined;
getChainProperties (): ChainProperties | undefined;
getDefinition (name: string): string | undefined;