Lazy decoration (part 9, rpc) (#4165)

* Lazy decoration (part 9, rpc)

* Flatten

* Cleanups (& bump dev, fix copy-to)

* Only RPC methods are lazy

* Decorate rpc-core lazily

* Adjust import

* Remove (now) invalid test
This commit is contained in:
Jaco
2021-11-04 15:59:05 +02:00
committed by GitHub
parent 1f38a38bd5
commit 6fb7989b41
5 changed files with 61 additions and 68 deletions
+28 -15
View File
@@ -10,7 +10,7 @@ import type { DecoratedMeta } from '@polkadot/types/metadata/decorate/types';
import type { StorageEntry } from '@polkadot/types/primitive/types';
import type { AnyFunction, AnyTuple, CallFunction, Codec, DefinitionRpc, DefinitionRpcSub, DetectCodec, IMethod, IStorageKey, Registry, RegistryError, RegistryTypes } from '@polkadot/types/types';
import type { SubmittableExtrinsic } from '../submittable/types';
import type { ApiDecoration, ApiInterfaceRx, ApiOptions, ApiTypes, AugmentedQuery, DecoratedErrors, DecoratedEvents, DecoratedRpc, DecoratedRpcSection, DecorateMethod, GenericStorageEntryFunction, PaginationOptions, QueryableConsts, QueryableStorage, QueryableStorageEntry, QueryableStorageEntryAt, QueryableStorageMulti, QueryableStorageMultiArg, SubmittableExtrinsicFunction, SubmittableExtrinsics } from '../types';
import type { ApiDecoration, ApiInterfaceRx, ApiOptions, ApiTypes, AugmentedQuery, DecoratedErrors, DecoratedEvents, DecoratedRpc, DecorateMethod, GenericStorageEntryFunction, PaginationOptions, QueryableConsts, QueryableStorage, QueryableStorageEntry, QueryableStorageEntryAt, QueryableStorageMulti, QueryableStorageMultiArg, SubmittableExtrinsicFunction, SubmittableExtrinsics } from '../types';
import type { VersionedRegistry } from './types';
import { BehaviorSubject, combineLatest, from, map, of, switchMap, tap, toArray } from 'rxjs';
@@ -361,30 +361,43 @@ export abstract class Decorate<ApiType extends ApiTypes> extends Events {
}
protected _decorateRpc<ApiType extends ApiTypes> (rpc: RpcCore & RpcInterface, decorateMethod: DecorateMethod<ApiType>, input: Partial<DecoratedRpc<ApiType, RpcInterface>> = {}): DecoratedRpc<ApiType, RpcInterface> {
const out = input as DecoratedRpc<ApiType, RpcInterface>;
const out: Record<string, Record<string, unknown>> = input;
const decorateFn = (section: string, method: string): unknown => {
const source = rpc[section as 'chain'][method as 'getHeader'];
const fn = decorateMethod(source, { methodName: method }) as Record<string, unknown>;
fn.meta = source.meta;
fn.raw = decorateMethod(source.raw, { methodName: method }) as unknown;
return fn;
};
for (let s = 0; s < rpc.sections.length; s++) {
const sectionName = rpc.sections[s] as keyof DecoratedRpc<ApiType, RpcInterface>;
const section = rpc.sections[s];
if (!(out as Record<string, unknown>)[sectionName]) {
const section = {} as DecoratedRpcSection<ApiType, RpcInterface[typeof sectionName]>;
if (!Object.prototype.hasOwnProperty.call(out, section)) {
const methods = Object.keys(rpc[section as 'chain']);
const decorateInternal = (method: string) =>
decorateFn(section, method);
for (let m = 0; m < methods.length; m++) {
const method = methods[m];
for (const [methodName, method] of Object.entries(rpc[sectionName])) {
// skip subscriptions where we have a non-subscribe interface
if (this.hasSubscriptions || !(methodName.startsWith('subscribe') || methodName.startsWith('unsubscribe'))) {
(section as Record<string, unknown>)[methodName] = decorateMethod(method, { methodName }) as unknown;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(section as Record<string, { raw: unknown }>)[methodName].raw = decorateMethod(method.raw, { methodName }) as unknown;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(section as Record<string, { meta: unknown }>)[methodName].meta = method.meta;
if (this.hasSubscriptions || !(method.startsWith('subscribe') || method.startsWith('unsubscribe'))) {
if (!Object.prototype.hasOwnProperty.call(out, section)) {
out[section] = {};
}
lazyMethod(out[section], method, decorateInternal);
}
}
(out as Record<string, unknown>)[sectionName] = section;
}
}
return out;
return out as DecoratedRpc<ApiType, RpcInterface>;
}
// only be called if supportMulti is true
+2 -2
View File
@@ -64,5 +64,5 @@ export interface PaginationOptions<A = unknown> {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type DecorateMethod<ApiType extends ApiTypes> =
<M extends (...args: any[]) => Observable<any>>(method: M, options?: DecorateMethodOptions) => any;
export type DecorateMethod<ApiType extends ApiTypes, T = any> =
<M extends (...args: any[]) => Observable<any>>(method: M, options?: DecorateMethodOptions) => T;
+29 -32
View File
@@ -7,12 +7,12 @@ import type { StorageKey, Vec } from '@polkadot/types';
import type { Hash } from '@polkadot/types/interfaces';
import type { AnyJson, Codec, DefinitionRpc, DefinitionRpcExt, DefinitionRpcSub, Registry } from '@polkadot/types/types';
import type { Memoized } from '@polkadot/util/types';
import type { RpcInterface, RpcInterfaceMethod } from './types';
import type { RpcInterfaceMethod } from './types';
import { Observable, publishReplay, refCount } from 'rxjs';
import { rpcDefinitions } from '@polkadot/types';
import { assert, hexToU8a, isFunction, isNull, isUndefined, logger, memoize, u8aToU8a } from '@polkadot/util';
import { assert, hexToU8a, isFunction, isNull, isUndefined, lazyMethod, logger, memoize, objectSpread, u8aToU8a } from '@polkadot/util';
import { drr, refCountDelay } from './util';
@@ -141,44 +141,41 @@ export class RpcCore {
});
}
public addUserInterfaces<Section extends keyof RpcInterface> (userRpc: Record<string, Record<string, DefinitionRpc | DefinitionRpcSub>>): void {
public addUserInterfaces (userRpc: Record<string, Record<string, DefinitionRpc | DefinitionRpcSub>>): void {
// add any extra user-defined sections
this.sections.push(...Object.keys(userRpc).filter((key) => !this.sections.includes(key)));
this.sections.push(...Object.keys(userRpc).filter((k) => !this.sections.includes(k)));
// decorate the sections with base and user methods
this.sections.forEach((sectionName): void => {
(this as Record<string, unknown>)[sectionName as Section] ||= {};
for (let s = 0; s < this.sections.length; s++) {
const section = this.sections[s];
const defs = objectSpread<Record<string, DefinitionRpc | DefinitionRpcSub>>({}, rpcDefinitions[section as 'babe'], userRpc[section]);
const methods = Object.keys(defs);
const section = (this as Record<string, unknown>)[sectionName as Section] as Record<string, unknown>;
for (let m = 0; m < methods.length; m++) {
const method = methods[m];
const def = defs[method];
Object
.entries({
...this._createInterface(sectionName, rpcDefinitions[sectionName as 'babe'] || {}),
...this._createInterface(sectionName, userRpc[sectionName] || {})
})
.forEach(([key, value]): void => {
section[key] ||= value;
});
});
if (!this.mapping.has(def.endpoint || `${section}_${method}`)) {
if (!(this as Record<string, unknown>)[section]) {
(this as Record<string, unknown>)[section] = {};
}
lazyMethod(this[section as 'connect'], method, () =>
this._createInterface(section, method, def)
);
}
}
}
}
private _createInterface<Section extends keyof RpcInterface> (section: string, methods: Record<string, DefinitionRpc | DefinitionRpcSub>): RpcInterface[Section] {
return Object
.entries(methods)
.filter(([method, { endpoint }]) => !this.mapping.has(endpoint || `${section}_${method}`))
.reduce((exposed, [method, { endpoint }]): RpcInterface[Section] => {
const def = methods[method];
const isSubscription = !!(def as DefinitionRpcSub).pubsub;
const jsonrpc = endpoint || `${section}_${method}`;
private _createInterface (section: string, method: string, def: DefinitionRpc | DefinitionRpcSub): RpcInterfaceMethod {
const isSubscription = !!(def as DefinitionRpcSub).pubsub;
const jsonrpc = def.endpoint || `${section}_${method}`;
this.mapping.set(jsonrpc, { ...def, isSubscription, jsonrpc, method, section });
this.mapping.set(jsonrpc, { ...def, isSubscription, jsonrpc, method, section });
(exposed as Record<string, unknown>)[method] = isSubscription
? this._createMethodSubscribe(section, method, def as DefinitionRpcSub)
: this._createMethodSend(section, method, def);
return exposed;
}, {} as RpcInterface[Section]);
return isSubscription
? this._createMethodSubscribe(section, method, def as DefinitionRpcSub)
: this._createMethodSend(section, method, def);
}
private _memomize (creator: <T> (isScale: boolean) => (...values: unknown[]) => Observable<T>, def: DefinitionRpc): Memoized<RpcInterfaceMethod> {
-18
View File
@@ -1,11 +1,8 @@
// Copyright 2017-2021 @polkadot/rpc-core authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Observable } from 'rxjs';
import type { RpcInterface } from '@polkadot/rpc-core/types.jsonrpc';
import { of } from 'rxjs';
import { MockProvider } from '@polkadot/rpc-provider/mock';
import { TypeRegistry } from '@polkadot/types/create';
@@ -25,21 +22,6 @@ describe('replay', (): void => {
await provider.disconnect();
});
it('subscribes via the rpc section', (done): void => {
// we don't honor types or number of params here
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(rpc.chain as any).getBlockHash = jest.fn((): Observable<number> => of(1));
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
(rpc.chain as any).getBlockHash(123, false).subscribe((): void => {
expect(
// eslint-disable-next-line @typescript-eslint/unbound-method
rpc.chain.getBlockHash
).toHaveBeenCalledWith(123, false);
done();
});
});
it('returns the observable value', (done): void => {
rpc.system.chain().subscribe((value: any): void => {
if (value) {
+2 -1
View File
@@ -4,11 +4,12 @@
// FIXME, this whole file needs to move to API
import type { Observable } from 'rxjs';
import type { AnyFunction } from '@polkadot/types/types';
import type { AnyFunction, DefinitionRpc } from '@polkadot/types/types';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface RpcInterface {}
export type AugmentedRpc<F extends AnyFunction> = F & {
raw: <T> (...params: Parameters<F>) => Observable<T>;
meta: DefinitionRpc;
};