From fadb1ffdd82f9810ed07c59ee8a2554183de36e8 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Tue, 1 Oct 2019 14:30:53 +0200 Subject: [PATCH] Make derive types work, and a bit more readable (#1430) * Fix typings for derive * Fix typings * Spacing --- packages/api-derive/src/accounts/indexes.ts | 2 +- packages/api-derive/src/balances/all.ts | 2 +- packages/api-derive/src/balances/fees.ts | 2 +- packages/api-derive/src/index.spec.ts | 4 +- packages/api-derive/src/index.ts | 25 ++++++----- packages/api/src/base/Decorate.ts | 48 ++++++++++----------- packages/api/src/base/Init.ts | 2 +- packages/api/src/base/index.ts | 4 +- packages/api/src/checkTypes.manual.ts | 2 +- packages/api/src/types.ts | 2 +- packages/api/src/util/decorate.ts | 42 ++++++++++-------- 11 files changed, 70 insertions(+), 65 deletions(-) diff --git a/packages/api-derive/src/accounts/indexes.ts b/packages/api-derive/src/accounts/indexes.ts index ff02725e8a..4d24e0b722 100644 --- a/packages/api-derive/src/accounts/indexes.ts +++ b/packages/api-derive/src/accounts/indexes.ts @@ -53,7 +53,7 @@ export function indexes (api: ApiInterfaceRx): () => Observable }); return result; - }, {} as unknown as AccountIndexes)), + }, {} as AccountIndexes)), drr() ); }; diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index 66ad6043ca..29a6cf70b9 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -53,7 +53,7 @@ function calcBalances ([accountId = createType('AccountId'), bestNumber = create reservedBalance, vestedBalance, votingBalance: freeBalance.add(reservedBalance) - } as unknown as DerivedBalances; + } as DerivedBalances; } /** diff --git a/packages/api-derive/src/balances/fees.ts b/packages/api-derive/src/balances/fees.ts index d4db94905d..352b56c065 100644 --- a/packages/api-derive/src/balances/fees.ts +++ b/packages/api-derive/src/balances/fees.ts @@ -62,7 +62,7 @@ export function fees (api: ApiInterfaceRx): () => Observable { transactionBaseFee, transactionByteFee, transferFee - } as unknown as DerivedFees)), + })), drr() ); }; diff --git a/packages/api-derive/src/index.spec.ts b/packages/api-derive/src/index.spec.ts index f273f51cf7..7b6176ba52 100644 --- a/packages/api-derive/src/index.spec.ts +++ b/packages/api-derive/src/index.spec.ts @@ -6,11 +6,11 @@ import { Observable, from } from 'rxjs'; import ApiRx from '@polkadot/api/rx/Api'; import MockProvider from '@polkadot/rpc-provider/mock'; -import { Derive } from '.'; +import { ExactDerive } from '.'; const testFunction = (api: ApiRx): any => { return < - Section extends keyof Derive, + Section extends keyof ExactDerive, Method extends keyof (typeof api.derive[Section]) >(section: Section, method: Method, inputs: any[]): void => { describe(`derive.${section}.${method}`, (): void => { diff --git a/packages/api-derive/src/index.ts b/packages/api-derive/src/index.ts index 42c8102c89..0391d7496a 100644 --- a/packages/api-derive/src/index.ts +++ b/packages/api-derive/src/index.ts @@ -19,14 +19,13 @@ import * as staking from './staking'; export * from './type'; -export type ReturnTypes
= { +type DeriveSection
= { [Method in keyof Section]: Section[Method] extends AnyFunction ? ReturnType // ReturnType will be the inner function, i.e. without (api) argument : never; }; - -export type DeriveSections = { - [Section in keyof AllSections]: ReturnTypes +type DeriveAllSections = { + [Section in keyof AllSections]: DeriveSection }; export type DeriveCustom = Record (...args: any[]) => Observable>>; @@ -35,15 +34,15 @@ export type DeriveCustom = Record (api: ApiInterfaceRx, allSections: AllSections): DeriveSections { +function injectFunctions (api: ApiInterfaceRx, allSections: AllSections): DeriveAllSections { return Object .keys(allSections) - .reduce((deriveAcc, sectionName): DeriveSections => { + .reduce((deriveAcc, sectionName): DeriveAllSections => { const section = allSections[sectionName as keyof AllSections]; deriveAcc[sectionName as keyof AllSections] = Object .keys(section) - .reduce((sectionAcc, _methodName): ReturnTypes => { + .reduce((sectionAcc, _methodName): DeriveSection => { const methodName = _methodName as keyof typeof section; // Not sure what to do here, casting as any. Though the final types are good const method = (section[methodName] as any)(api); @@ -51,18 +50,18 @@ function injectFunctions (api: ApiInterfaceRx, allSections: AllSect (sectionAcc as any)[methodName] = method; return sectionAcc; - }, {} as unknown as ReturnTypes); + }, {} as DeriveSection); return deriveAcc; - }, {} as unknown as DeriveSections); + }, {} as DeriveAllSections); } export const derive = { accounts, balances, chain, contracts, democracy, elections, imOnline, session, staking }; -export type Derive = typeof derive; +export type ExactDerive = DeriveAllSections; -// FIXME I have no idea how to get this done -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export default function decorateDerive (api: ApiInterfaceRx, custom: DeriveCustom = {}) { +// FIXME The return type of this function should be {...ExactDerive, ...DeriveCustom} +// For now we just drop the custom derive typings +export default function decorateDerive (api: ApiInterfaceRx, custom: DeriveCustom = {}): ExactDerive { return { ...injectFunctions(api, derive), ...injectFunctions(api, custom) diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 3fe20cc9c2..4cda84554e 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -4,18 +4,20 @@ import { RpcMethod } from '@polkadot/jsonrpc/types'; import { RpcInterface } from '@polkadot/rpc-core/jsonrpc.types'; -// Unfortunately we cannot import individual interfaces here, because the -// return type of `decorateDerive` needs to have all interfaces. See -// https://github.com/polkadot-js/api/issues/1425 for more info. -import * as t from '@polkadot/types/interfaces'; +import { Call, Hash, RuntimeVersion } from '@polkadot/types/interfaces'; import { AnyFunction, CallFunction, Codec, CodecArg as Arg, ModulesWithCalls } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '../submittable/types'; -import { ApiInterfaceRx, ApiOptions, ApiTypes, DecorateMethod, DecoratedRpc, DecoratedRpcSection, QueryableModuleStorage, QueryableStorage, QueryableStorageEntry, QueryableStorageMulti, QueryableStorageMultiArg, SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics } from '../types'; +import { + ApiInterfaceRx, ApiOptions, ApiTypes, + DecorateMethod, DecoratedRpc, DecoratedRpcSection, + QueryableModuleStorage, QueryableStorage, QueryableStorageEntry, QueryableStorageMulti, QueryableStorageMultiArg, + SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics +} from '../types'; import BN from 'bn.js'; import { BehaviorSubject, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import decorateDerive from '@polkadot/api-derive'; +import decorateDerive, { ExactDerive } from '@polkadot/api-derive'; import { Constants } from '@polkadot/api-metadata/consts/types'; import { Storage } from '@polkadot/api-metadata/storage/types'; import jsonrpc from '@polkadot/jsonrpc'; @@ -28,7 +30,7 @@ import { StorageEntry } from '@polkadot/types/primitive/StorageKey'; import { compactStripLength, u8aToHex } from '@polkadot/util'; import { createSubmittable } from '../submittable'; -import { decorateSections } from '../util/decorate'; +import { decorateSections, DeriveAllSections } from '../util/decorate'; import Events from './Events'; interface MetaDecoration { @@ -51,7 +53,7 @@ export default abstract class Decorate extends Events { protected _extrinsicType: number = EXTRINSIC_DEFAULT_VERSION; - protected _genesisHash?: t.Hash; + protected _genesisHash?: Hash; protected _isConnected: BehaviorSubject; @@ -71,7 +73,7 @@ export default abstract class Decorate extends Events { protected _runtimeMetadata?: Metadata; - protected _runtimeVersion?: t.RuntimeVersion; + protected _runtimeVersion?: RuntimeVersion; protected _rx: Partial = {}; @@ -93,7 +95,7 @@ export default abstract class Decorate extends Events { * implemented by transforming the Observable to Stream/Iterator/Kefir/Bacon * via `deocrateMethod`. */ - protected decorateMethod: DecorateMethod; + protected decorateMethod: DecorateMethod; /** * @description Create an instance of the class @@ -113,7 +115,7 @@ export default abstract class Decorate extends Events { * }); * ``` */ - public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { + public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { super(); const thisProvider = options.source @@ -173,7 +175,7 @@ export default abstract class Decorate extends Events { }); } - protected decorateRpc (rpc: RpcCore, decorateMethod: Decorate['decorateMethod']): DecoratedRpc { + protected decorateRpc (rpc: RpcCore, decorateMethod: DecorateMethod): DecoratedRpc { return Object.keys(jsonrpc).reduce((out, _sectionName): DecoratedRpc => { const sectionName = _sectionName as keyof DecoratedRpc; @@ -191,7 +193,7 @@ export default abstract class Decorate extends Events { }, {} as unknown as DecoratedRpc); } - protected decorateMulti (decorateMethod: Decorate['decorateMethod']): QueryableStorageMulti { + protected decorateMulti (decorateMethod: DecorateMethod): QueryableStorageMulti { return decorateMethod((calls: QueryableStorageMultiArg[]): Observable => this._rpcCore.state.subscribeStorage( calls.map((arg: QueryableStorageMultiArg): [QueryableStorageEntry, ...Arg[]] => @@ -201,7 +203,7 @@ export default abstract class Decorate extends Events { : [arg.creator] as any))); } - protected decorateExtrinsics (extrinsics: ModulesWithCalls, decorateMethod: Decorate['decorateMethod']): SubmittableExtrinsics { + protected decorateExtrinsics (extrinsics: ModulesWithCalls, decorateMethod: DecorateMethod): SubmittableExtrinsics { const creator = createSubmittable(this._type, this._rx as ApiInterfaceRx, decorateMethod); return Object.entries(extrinsics).reduce((out, [name, section]): SubmittableExtrinsics => { @@ -215,14 +217,14 @@ export default abstract class Decorate extends Events { }, creator as unknown as SubmittableExtrinsics); } - private decorateExtrinsicEntry (method: CallFunction, creator: (value: t.Call | Uint8Array | string) => SubmittableExtrinsic): SubmittableExtrinsicFunction { + private decorateExtrinsicEntry (method: CallFunction, creator: (value: Call | Uint8Array | string) => SubmittableExtrinsic): SubmittableExtrinsicFunction { const decorated = (...params: Arg[]): SubmittableExtrinsic => creator(method(...params)); return this.decorateFunctionMeta(method, decorated as any) as SubmittableExtrinsicFunction; } - protected decorateStorage (storage: Storage, decorateMethod: Decorate['decorateMethod']): QueryableStorage { + protected decorateStorage (storage: Storage, decorateMethod: DecorateMethod): QueryableStorage { return Object.entries(storage).reduce((out, [name, section]): QueryableStorage => { out[name] = Object.entries(section).reduce((out, [name, method]): QueryableModuleStorage => { out[name] = this.decorateStorageEntry(method, decorateMethod); @@ -234,7 +236,7 @@ export default abstract class Decorate extends Events { }, {} as unknown as QueryableStorage); } - private decorateStorageEntry (creator: StorageEntry, decorateMethod: Decorate['decorateMethod']): QueryableStorageEntry { + private decorateStorageEntry (creator: StorageEntry, decorateMethod: DecorateMethod): QueryableStorageEntry { // get the storage arguments, with DoubleMap as an array entry, otherwise spread const getArgs = (...args: any[]): any[] => creator.meta.type.isDoubleMap ? [creator, args] @@ -252,10 +254,10 @@ export default abstract class Decorate extends Events { decorated.creator = creator; - decorated.at = decorateMethod((hash: t.Hash, arg1?: Arg, arg2?: Arg): Observable => + decorated.at = decorateMethod((hash: Hash, arg1?: Arg, arg2?: Arg): Observable => this._rpcCore.state.getStorage(getArgs(arg1, arg2), hash)); - decorated.hash = decorateMethod((arg1?: Arg, arg2?: Arg): Observable => + decorated.hash = decorateMethod((arg1?: Arg, arg2?: Arg): Observable => this._rpcCore.state.getStorageHash(getArgs(arg1, arg2))); decorated.key = (arg1?: Arg, arg2?: Arg): string => @@ -272,7 +274,7 @@ export default abstract class Decorate extends Events { return this.decorateFunctionMeta(creator, decorated) as unknown as QueryableStorageEntry; } - private decorateStorageEntryLinked (creator: StorageEntry, decorateMethod: Decorate['decorateMethod']): ReturnType['decorateMethod']> { + private decorateStorageEntryLinked (creator: StorageEntry, decorateMethod: DecorateMethod): ReturnType> { const result: Map]> = new Map(); let subject: BehaviorSubject; let head: Codec | null = null; @@ -340,13 +342,11 @@ export default abstract class Decorate extends Events { ); } - // FIXME Any better ideas? `any` doesn't work, needs a return but TS infers - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - protected decorateDerive (apiRx: ApiInterfaceRx, decorateMethod: Decorate['decorateMethod']) { + protected decorateDerive (apiRx: ApiInterfaceRx, decorateMethod: DecorateMethod): DeriveAllSections { // Pull in derive from api-derive const derive = decorateDerive(apiRx, this._options.derives); - return decorateSections(derive, decorateMethod); + return decorateSections(derive, decorateMethod); } /** diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index f68db7df65..792ea4cc55 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -56,7 +56,7 @@ const l = logger('api/decorator'); export default abstract class Init extends Decorate { private _healthTimer: NodeJS.Timeout | null = null; - public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { + public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { super(options, type, decorateMethod); assert(this._rpcCore.provider.hasSubscriptions, 'Api can only be used with a provider supporting subscriptions'); diff --git a/packages/api/src/base/index.ts b/packages/api/src/base/index.ts index bf88f08225..2198e3b307 100644 --- a/packages/api/src/base/index.ts +++ b/packages/api/src/base/index.ts @@ -26,7 +26,7 @@ try { pkgJson = { name: '@polkadot/api', version: '-' }; } -function assertResult (value: T | undefined): T { +function assertResult (value: T | undefined): T { assert(!isUndefined(value), 'Api needs to be initialised before using, listen on \'ready\''); return value as T; @@ -51,7 +51,7 @@ export default abstract class ApiBase extends Init { * }); * ``` */ - public constructor (options: ApiOptions = {}, type: ApiTypes, decorateMethod: DecorateMethod) { + public constructor (options: ApiOptions = {}, type: ApiTypes, decorateMethod: DecorateMethod) { super(options, type, decorateMethod); } diff --git a/packages/api/src/checkTypes.manual.ts b/packages/api/src/checkTypes.manual.ts index 1e608770f1..b8465abf90 100644 --- a/packages/api/src/checkTypes.manual.ts +++ b/packages/api/src/checkTypes.manual.ts @@ -28,7 +28,7 @@ async function derive (api: ApiPromise): Promise { console.log('current author:', header.author); }); - const fees = api.derive.balances.fees(); + const fees = await api.derive.balances.fees(); console.log('fees', fees); } diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 0b75883c8b..9c7f6a6318 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -50,7 +50,7 @@ export interface DecorateMethodOptions { methodName?: string; } -export type DecorateMethod = (method: (...args: any[]) => Observable, options?: DecorateMethodOptions) => any; +export type DecorateMethod = Observable>(method: Method, options?: DecorateMethodOptions) => any; // Here are the return types of these parts of the api: // - api.query.*.*: no exact typings diff --git a/packages/api/src/util/decorate.ts b/packages/api/src/util/decorate.ts index c1311c74a3..b44fe5f58a 100644 --- a/packages/api/src/util/decorate.ts +++ b/packages/api/src/util/decorate.ts @@ -3,7 +3,14 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { AnyFunction } from '@polkadot/types/types'; -import { MethodResult } from '../types'; +import { DecorateMethod, MethodResult } from '../types'; + +// Most generic typings for `api.derive.*.*` +type AnyDerive = Record>; +// Exact typings for a particular section `api.derive.section.*` +type DeriveSection> = { [MethodName in keyof Section]: MethodResult }; +// Exact typings for all sections `api.derive.*.*` +export type DeriveAllSections = { [SectionName in keyof AllSections]: DeriveSection }; // A technically unsafe version of Object.keys(obj) that assumes that // obj only has known properties of T @@ -11,45 +18,44 @@ function keys (obj: T): (keyof T)[] { return Object.keys(obj) as (keyof T)[]; } -// FIXME the return value typings here is horrible and not DRY -function decorateMethods any>> ( +/** + * This is a methods decorator which keeps all type information. + */ +function decorateMethods> ( section: Section, - decorateMethod: (method: Method) => MethodResult -): { [MethodName in keyof Section]: MethodResult } { + decorateMethod: DecorateMethod +): DeriveSection { return keys(section).reduce( ( - acc: { [MethodName in keyof Section]: MethodResult }, + acc: DeriveSection, methodName: MethodName - ): { [MethodName in keyof Section]: MethodResult } => { + ): DeriveSection => { const method = section[methodName]; - acc[methodName] = decorateMethod(method) as any; + acc[methodName] = decorateMethod(method); return acc; }, - {} as unknown as { [MethodName in keyof Section]: MethodResult } + {} as DeriveSection ); } /** * This is a section decorator which keeps all type information. */ -// FIXME the return value typings here is horrible and not DRY -export function decorateSections any> -}> ( +export function decorateSections ( allSections: AllSections, - decorateMethod: (method: Method) => MethodResult -): { [SectionName in keyof AllSections]: { [MethodName in keyof AllSections[SectionName]]: MethodResult } } { + decorateMethod: DecorateMethod +): DeriveAllSections { return keys(allSections).reduce( ( - acc: { [SectionName in keyof AllSections]: { [MethodName in keyof AllSections[SectionName]]: MethodResult } }, + acc: DeriveAllSections, sectionName: MethodName - ): { [SectionName in keyof AllSections]: { [MethodName in keyof AllSections[SectionName]]: MethodResult } } => { + ): DeriveAllSections => { acc[sectionName] = decorateMethods(allSections[sectionName], decorateMethod); return acc; }, - {} as unknown as { [SectionName in keyof AllSections]: { [MethodName in keyof AllSections[SectionName]]: MethodResult } } + {} as DeriveAllSections ); }