From 6fb7989b41327eba09cc05fab16ca0c257dcf12f Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 4 Nov 2021 14:59:05 +0100 Subject: [PATCH] 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 --- packages/api/src/base/Decorate.ts | 43 +++++++++++------- packages/api/src/types/base.ts | 4 +- packages/rpc-core/src/bundle.ts | 61 ++++++++++++-------------- packages/rpc-core/src/replay.spec.ts | 18 -------- packages/rpc-core/src/types.jsonrpc.ts | 3 +- 5 files changed, 61 insertions(+), 68 deletions(-) diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 3f49eb022f..3cbfacffa1 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -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 extends Events { } protected _decorateRpc (rpc: RpcCore & RpcInterface, decorateMethod: DecorateMethod, input: Partial> = {}): DecoratedRpc { - const out = input as DecoratedRpc; + const out: Record> = 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; + + 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; + const section = rpc.sections[s]; - if (!(out as Record)[sectionName]) { - const section = {} as DecoratedRpcSection; + 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)[methodName] = decorateMethod(method, { methodName }) as unknown; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - (section as Record)[methodName].raw = decorateMethod(method.raw, { methodName }) as unknown; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - (section as Record)[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)[sectionName] = section; } } - return out; + return out as DecoratedRpc; } // only be called if supportMulti is true diff --git a/packages/api/src/types/base.ts b/packages/api/src/types/base.ts index d103becc43..b2b28eb3e6 100644 --- a/packages/api/src/types/base.ts +++ b/packages/api/src/types/base.ts @@ -64,5 +64,5 @@ export interface PaginationOptions { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export type DecorateMethod = - Observable>(method: M, options?: DecorateMethodOptions) => any; +export type DecorateMethod = + Observable>(method: M, options?: DecorateMethodOptions) => T; diff --git a/packages/rpc-core/src/bundle.ts b/packages/rpc-core/src/bundle.ts index 41bc0ebae1..965456a3a9 100644 --- a/packages/rpc-core/src/bundle.ts +++ b/packages/rpc-core/src/bundle.ts @@ -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
(userRpc: Record>): void { + public addUserInterfaces (userRpc: Record>): 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)[sectionName as Section] ||= {}; + for (let s = 0; s < this.sections.length; s++) { + const section = this.sections[s]; + const defs = objectSpread>({}, rpcDefinitions[section as 'babe'], userRpc[section]); + const methods = Object.keys(defs); - const section = (this as Record)[sectionName as Section] as Record; + 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)[section]) { + (this as Record)[section] = {}; + } + + lazyMethod(this[section as 'connect'], method, () => + this._createInterface(section, method, def) + ); + } + } + } } - private _createInterface
(section: string, methods: Record): 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)[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: (isScale: boolean) => (...values: unknown[]) => Observable, def: DefinitionRpc): Memoized { diff --git a/packages/rpc-core/src/replay.spec.ts b/packages/rpc-core/src/replay.spec.ts index 19f382b26f..f6c9c16d9e 100644 --- a/packages/rpc-core/src/replay.spec.ts +++ b/packages/rpc-core/src/replay.spec.ts @@ -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 => 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) { diff --git a/packages/rpc-core/src/types.jsonrpc.ts b/packages/rpc-core/src/types.jsonrpc.ts index eecf588d9c..4e72887f26 100644 --- a/packages/rpc-core/src/types.jsonrpc.ts +++ b/packages/rpc-core/src/types.jsonrpc.ts @@ -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 & { raw: (...params: Parameters) => Observable; + meta: DefinitionRpc; };