diff --git a/packages/api-derive/README.md b/packages/api-derive/README.md index 8da6b08285..6373fa5017 100644 --- a/packages/api-derive/README.md +++ b/packages/api-derive/README.md @@ -1,3 +1,5 @@ # @polkadot/api-derive -Common functions used across Polkadot, derived from RPC calls and storage queries. +Collection of high-level utility functions built on top of the @polkadot/api library. Designed to simplify the process of querying complex on-chain data by combining multiple RPC calls, storage queries, and runtime logic into a single, callable function. + +Instead of manually fetching and processing blockchain data, developers can use `api.derive` methods to retrieve information. diff --git a/packages/api-derive/src/accounts/accountId.ts b/packages/api-derive/src/accounts/accountId.ts index ac3fcc8121..1db63b99d0 100644 --- a/packages/api-derive/src/accounts/accountId.ts +++ b/packages/api-derive/src/accounts/accountId.ts @@ -14,7 +14,7 @@ import { memo } from '../util/index.js'; /** * @name accountId - * @param {(Address | AccountId | AccountIndex | string | null)} address - An accounts address in various formats. + * @param {(Address | AccountId | AccountIndex | string | null)} address An accounts address in various formats. * @description An [[AccountId]] */ export function accountId (instanceId: string, api: DeriveApi): (address?: Address | AccountId | AccountIndex | string | null) => Observable { diff --git a/packages/api-derive/src/accounts/info.ts b/packages/api-derive/src/accounts/info.ts index cd736e9e65..21212a10c0 100644 --- a/packages/api-derive/src/accounts/info.ts +++ b/packages/api-derive/src/accounts/info.ts @@ -29,7 +29,7 @@ function retrieveNick (api: DeriveApi, accountId?: AccountId): Observable Observable { return memo(instanceId, (address?: AccountIndex | AccountId | Address | Uint8Array | string | null): Observable => diff --git a/packages/api-derive/src/staking/account.ts b/packages/api-derive/src/staking/account.ts index 20e36f9b51..b26b89a52c 100644 --- a/packages/api-derive/src/staking/account.ts +++ b/packages/api-derive/src/staking/account.ts @@ -64,6 +64,9 @@ function parseResult (api: DeriveApi, sessionInfo: DeriveSessionInfo, keys: Deri } /** + * @name accounts + * @param {(Uint8Array | string)[]} accountIds List of account stashes + * @param {StakingQueryFlags} opts optional filtering flag * @description From a list of stashes, fill in all the relevant staking details */ export function accounts (instanceId: string, api: DeriveApi): (accountIds: (Uint8Array | string)[], opts?: StakingQueryFlags) => Observable { @@ -84,6 +87,9 @@ export function accounts (instanceId: string, api: DeriveApi): (accountIds: (Uin } /** + * @name account + * @param {(Uint8Array | string)} accountId AccountId of the stash + * @param {StakingQueryFlags} opts optional filtering flag * @description From a stash, retrieve the controllerId and fill in all the relevant staking details */ export const account = /*#__PURE__*/ firstMemo( diff --git a/packages/typegen/package.json b/packages/typegen/package.json index d6b6baeffe..8d24222d39 100644 --- a/packages/typegen/package.json +++ b/packages/typegen/package.json @@ -30,6 +30,7 @@ "dependencies": { "@polkadot/api": "15.6.2-1-x", "@polkadot/api-augment": "15.6.2-1-x", + "@polkadot/api-derive": "15.6.2-1-x", "@polkadot/rpc-augment": "15.6.2-1-x", "@polkadot/rpc-provider": "15.6.2-1-x", "@polkadot/types": "15.6.2-1-x", @@ -40,6 +41,7 @@ "@polkadot/util": "^13.4.3", "@polkadot/util-crypto": "^13.4.3", "@polkadot/x-ws": "^13.4.3", + "comment-parser": "^1.4.1", "handlebars": "^4.7.8", "tslib": "^2.8.1", "yargs": "^17.7.2" diff --git a/packages/typegen/src/metadataMd.ts b/packages/typegen/src/metadataMd.ts index bb7d376605..209a8bd758 100644 --- a/packages/typegen/src/metadataMd.ts +++ b/packages/typegen/src/metadataMd.ts @@ -7,12 +7,14 @@ import type { Text } from '@polkadot/types/primitive'; import type { Codec, DefinitionCall, DefinitionRpcParam, DefinitionsCall, Registry } from '@polkadot/types/types'; import type { HexString } from '@polkadot/util/types'; +import { parse, type Spec } from 'comment-parser'; import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; +import { derive } from '@polkadot/api-derive'; import { Metadata, TypeRegistry, Vec } from '@polkadot/types'; import * as definitions from '@polkadot/types/interfaces/definitions'; import { getStorage as getSubstrateStorage } from '@polkadot/types/metadata/decorate/storage/getStorage'; @@ -62,6 +64,20 @@ interface StaticDef { ver?: { apis: ApiDef[] } } +interface Derive { + name: string | null; + description: string | null; + params: DeriveParam[]; + returns: string | null; + example: string | null; +} + +interface DeriveParam { + description: string | null; + name: string | null; + type: string | null; +} + const headerFn = (runtimeDesc: string) => `\n\n(NOTE: These were generated from a static/snapshot view of a recent ${runtimeDesc}. Some items may not be available in older nodes, or in any customized implementations.)`; const ALL_STATIC: Record = { @@ -547,6 +563,169 @@ function addErrors (runtimeDesc: string, { lookup, pallets }: MetadataLatest): s }); } +const BASE_DERIVE_PATH = '../api/packages/api-derive/src/'; + +// It finds all typescript file paths withing a given derive module. +const obtainDeriveFiles = (deriveModule: string) => { + const filePath = `${BASE_DERIVE_PATH}${deriveModule}`; + const files = fs.readdirSync(filePath); + + return files + .filter((file) => file.endsWith('.ts') && !file.endsWith('.d.ts')) + .map((file) => `${deriveModule}/${file}`); +}; + +function extractDeriveDescription (tags: Spec[], name: string) { + const descriptionTag = tags.find((tag) => tag.tag === name); + + return descriptionTag + ? `${descriptionTag.name ?? ''} ${descriptionTag.description ?? ''}`.trim() + : null; +} + +function extractDeriveParams (tags: Spec[]) { + const descriptionTag = tags + .filter((tag) => tag.tag === 'param') + .map((param) => { + return { + description: param.description ?? null, + name: param.name ?? null, + type: param.type ?? null + }; + }); + + return descriptionTag; +} + +function extractDeriveExample (tags: Spec[]) { + const exampleTag = tags.find((tag) => tag.tag === 'example'); + + if (!exampleTag) { + return null; + } + + let example = ''; + const inCodeBlock = { done: false, found: false }; + + // / Obtain code block from example tag. + exampleTag.source.forEach((line) => { + if (inCodeBlock.done) { + return; + } + + if (line.source.indexOf('```') !== -1 && !inCodeBlock.found) { + inCodeBlock.found = true; + } else if (line.source.indexOf('```') !== -1 && inCodeBlock.found) { + inCodeBlock.done = true; + } + + if (!inCodeBlock.found) { + return; + } + + example += line.source.slice(2, line.source.length); + + if (!inCodeBlock.done) { + example += '\n'; + } + }); + + return example; +} + +// Parses the comments of a given derive file and adds the +// relevant information (name, description, params, returns, example). +const getDeriveDocs = ( + metadata: Record, + file: string +) => { + const filePath = `${BASE_DERIVE_PATH}${file}`; + const deriveModule = file.split('/')[0]; + const fileContent = fs.readFileSync(filePath, 'utf8'); + const comments = parse(fileContent); + + const docs: Derive[] = comments + .filter((comment) => comment.tags) + .map((comment) => { + return { + description: extractDeriveDescription(comment.tags, 'description'), + example: extractDeriveExample(comment.tags), + name: comment.tags.find((tag) => tag.tag === 'name')?.name || null, + params: extractDeriveParams(comment.tags), + returns: extractDeriveDescription(comment.tags, 'returns') + }; + }); + + metadata[deriveModule] + ? (metadata[deriveModule] = [...metadata[deriveModule], ...docs]) + : (metadata[deriveModule] = [...docs]); +}; + +function renderDerives (metadata: Record) { + let md = '---\ntitle: Derives\n---\n\nThis page lists the derives that can be encountered in the different modules. Designed to simplify the process of querying complex on-chain data by combining multiple RPC calls, storage queries, and runtime logic into a single, callable function. \n\nInstead of manually fetching and processing blockchain data, developers can use `api.derive..()` to retrieve information.\n\n'; + const deriveModules = Object.keys(metadata).filter( + (d) => metadata[d].length !== 0 + ); + + // index + deriveModules.forEach((deriveModule) => { + md += `- **[${deriveModule}](#${deriveModule})**\n\n`; + }); + + // contents + deriveModules.forEach((deriveModule) => { + md += `\n___\n## ${deriveModule}\n`; + + metadata[deriveModule] + .filter((item) => item.name) + .forEach((item) => { + const { description, example, name, params, returns } = item; + + md += ` \n### [${name}](#${name})`; + + if (description) { + md += `\n${description}`; + } + + md += `\n- **interface**: \`api.derive.${deriveModule}.${name}\``; + + if (params.length) { + md += '\n- **params**:\n'; + params.forEach( + (param) => + (md += ` - ${param.name} \`${param.type}\`: ${param.description}`) + ); + } + + if (returns) { + md += `\n- **returns**: ${returns}`; + } + + if (example) { + md += `\n- **example**: \n${example}`; + } + }); + }); + + return md; +} + +function generateDerives () { + let fileList: string[] = []; + + Object.keys(derive).forEach((deriveModule) => { + fileList = [...fileList, ...obtainDeriveFiles(deriveModule)]; + }); + + const metadata = {}; + + fileList.forEach((file) => { + getDeriveDocs(metadata, file); + }); + + return renderDerives(metadata); +} + /** @internal */ function writeFile (name: string, ...chunks: any[]): void { const writeStream = fs.createWriteStream(name, { encoding: 'utf8', flags: 'w' }); @@ -642,6 +821,10 @@ async function mainPromise (): Promise { writeFile(`${docRoot}/extrinsics.md`, addExtrinsics(runtimeDesc, latest)); writeFile(`${docRoot}/events.md`, addEvents(runtimeDesc, latest)); writeFile(`${docRoot}/errors.md`, addErrors(runtimeDesc, latest)); + + if (chainName === 'Substrate') { + writeFile('docs/derives/derives.md', generateDerives()); + } } export function main (): void { diff --git a/packages/typegen/tsconfig.build.json b/packages/typegen/tsconfig.build.json index 609fe93b37..c8fe76402e 100644 --- a/packages/typegen/tsconfig.build.json +++ b/packages/typegen/tsconfig.build.json @@ -16,6 +16,7 @@ "references": [ { "path": "../api/tsconfig.build.json" }, { "path": "../api-augment/tsconfig.build.json" }, + { "path": "../api-derive/tsconfig.build.json" }, { "path": "../rpc-augment/tsconfig.build.json" }, { "path": "../rpc-provider/tsconfig.build.json" }, { "path": "../types/tsconfig.build.json" }, diff --git a/yarn.lock b/yarn.lock index 57fdf0650d..84045b41d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -712,6 +712,7 @@ __metadata: dependencies: "@polkadot/api": "npm:15.6.2-1-x" "@polkadot/api-augment": "npm:15.6.2-1-x" + "@polkadot/api-derive": "npm:15.6.2-1-x" "@polkadot/rpc-augment": "npm:15.6.2-1-x" "@polkadot/rpc-provider": "npm:15.6.2-1-x" "@polkadot/types": "npm:15.6.2-1-x" @@ -723,6 +724,7 @@ __metadata: "@polkadot/util-crypto": "npm:^13.4.3" "@polkadot/x-ws": "npm:^13.4.3" "@types/yargs": "npm:^17.0.33" + comment-parser: "npm:^1.4.1" handlebars: "npm:^4.7.8" tslib: "npm:^2.8.1" yargs: "npm:^17.7.2" @@ -2919,6 +2921,13 @@ __metadata: languageName: node linkType: hard +"comment-parser@npm:^1.4.1": + version: 1.4.1 + resolution: "comment-parser@npm:1.4.1" + checksum: 10/16a3260b5e77819ebd9c99b0b65c7d6723b1ff73487bac9ce2d8f016a2847dd689e8663b88e1fad1444bbea89847c42f785708ac86a2c55f614f7095249bbf6b + languageName: node + linkType: hard + "commondir@npm:^1.0.1": version: 1.0.1 resolution: "commondir@npm:1.0.1"