From ebd1dad9bf418e0d513d2ec797c806476ead2651 Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Mon, 3 Jun 2024 07:52:35 +0200 Subject: [PATCH] Api fetching status indicator on tty (#768) --- packages/chopsticks/src/context.ts | 4 ++++ packages/chopsticks/src/logger.ts | 25 +++++++++++++++++++++++ packages/core/src/api.ts | 32 +++++++++++++++++++++--------- packages/core/src/setup.ts | 7 +++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/chopsticks/src/context.ts b/packages/chopsticks/src/context.ts index 16fa21ff..2cf74ebc 100644 --- a/packages/chopsticks/src/context.ts +++ b/packages/chopsticks/src/context.ts @@ -3,6 +3,7 @@ import { BlockEntry, GenesisProvider, defaultLogger, isUrl, setup, timeTravel } import { Config } from './schema/index.js' import { HexString } from '@polkadot/util/types' import { SqliteDatabase } from '@acala-network/chopsticks-db' +import { apiFetching } from './logger.js' import { overrideStorage, overrideWasm } from './utils/override.js' import { startFetchStorageWorker } from './utils/fetch-storages.js' import axios from 'axios' @@ -48,6 +49,9 @@ export const setupContext = async (argv: Config, overrideParent = false) => { offchainWorker: argv['offchain-worker'], maxMemoryBlockCount: argv['max-memory-block-count'], processQueuedMessages: argv['process-queued-messages'], + hooks: { + apiFetching, + }, }) // load block from db diff --git a/packages/chopsticks/src/logger.ts b/packages/chopsticks/src/logger.ts index 16046098..0c6a366c 100644 --- a/packages/chopsticks/src/logger.ts +++ b/packages/chopsticks/src/logger.ts @@ -1 +1,26 @@ +import _ from 'lodash' + export { defaultLogger, truncate } from '@acala-network/chopsticks-core' + +const showProgress = process.stdout.isTTY && !process.env['CI'] && !process.env['TEST'] + +export const spinnerFrames = + process.platform === 'win32' ? ['-', '\\', '|', '/'] : ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] +let index = 0 + +// clear to the right from cursor +const clearStatus = _.debounce(() => process.stdout.clearLine(1), 500, { trailing: true }) + +export const apiFetching = _.throttle( + () => { + if (!showProgress) return + + // print ` ⠋ Fetching|` and move cursor at position 0 of the line `| ⠋ Fetching` + process.stdout.write(` ${spinnerFrames[index++]} Fetching`) + process.stdout.cursorTo(0) + index = ++index % spinnerFrames.length + clearStatus() + }, + 50, + { leading: true }, +) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 36a290d3..cae493f1 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -26,6 +26,10 @@ export class Api { readonly signedExtensions: ExtDef + #apiHooks: { + fetching?: () => void + } = {} + constructor(provider: ProviderInterface, signedExtensions?: ExtDef) { this.#provider = provider this.signedExtensions = signedExtensions || {} @@ -68,20 +72,30 @@ export class Api { return this.#chainProperties } + // Set the hook function to be called when api fetch endpoint. + onFetching(fetching?: () => void) { + this.#apiHooks.fetching = fetching + } + + async send(method: string, params: unknown[], isCacheable?: boolean): Promise { + this.#apiHooks?.fetching?.() + return this.#provider.send(method, params, isCacheable) + } + async getSystemName() { - return this.#provider.send('system_name', []) + return this.send('system_name', []) } async getSystemProperties() { - return this.#provider.send('system_properties', []) + return this.send('system_properties', []) } async getSystemChain() { - return this.#provider.send('system_chain', []) + return this.send('system_chain', []) } async getBlockHash(blockNumber?: number) { - return this.#provider.send( + return this.send( 'chain_getBlockHash', Number.isInteger(blockNumber) ? [blockNumber] : [], !!blockNumber, @@ -89,11 +103,11 @@ export class Api { } async getHeader(hash?: string) { - return this.#provider.send
('chain_getHeader', hash ? [hash] : [], !!hash) + return this.send
('chain_getHeader', hash ? [hash] : [], !!hash) } async getBlock(hash?: string) { - return this.#provider.send('chain_getBlock', hash ? [hash] : [], !!hash) + return this.send('chain_getBlock', hash ? [hash] : [], !!hash) } async getStorage(key: string, hash?: string) { @@ -102,12 +116,12 @@ export class Api { // child storage key, use childstate_getStorage const params = [child, storageKey] if (hash) params.push(hash as HexString) - return this.#provider.send('childstate_getStorage', params, !!hash) + return this.send('childstate_getStorage', params, !!hash) } else { // main storage key, use state_getStorage const params = [key] if (hash) params.push(hash) - return this.#provider.send('state_getStorage', params, !!hash) + return this.send('state_getStorage', params, !!hash) } } @@ -125,7 +139,7 @@ export class Api { // main storage key, use state_getKeysPaged const params = [prefix, pageSize, startKey] if (hash) params.push(hash) - return this.#provider.send('state_getKeysPaged', params, !!hash) + return this.send('state_getKeysPaged', params, !!hash) } } diff --git a/packages/core/src/setup.ts b/packages/core/src/setup.ts index 5b9a499d..bf4cf088 100644 --- a/packages/core/src/setup.ts +++ b/packages/core/src/setup.ts @@ -25,6 +25,9 @@ export type SetupOptions = { offchainWorker?: boolean maxMemoryBlockCount?: number processQueuedMessages?: boolean + hooks?: { + apiFetching?: () => void + } } export const processOptions = async (options: SetupOptions) => { @@ -39,6 +42,10 @@ export const processOptions = async (options: SetupOptions) => { provider = new WsProvider(options.endpoint, 3_000) } const api = new Api(provider) + + // setup api hooks + api.onFetching(options.hooks?.apiFetching) + await api.isReady let blockHash: string