Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api-Derives documentation generator #6099

Merged
merged 13 commits into from
Feb 21, 2025
4 changes: 3 additions & 1 deletion packages/api-derive/README.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/api-derive/src/accounts/accountId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccountId> {
Expand Down
2 changes: 1 addition & 1 deletion packages/api-derive/src/accounts/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function retrieveNick (api: DeriveApi, accountId?: AccountId): Observable<string

/**
* @name info
* @description Returns aux. info with regards to an account, current that includes the accountId, accountIndex and nickname
* @description Returns aux. info with regards to an account, current that includes the accountId, accountIndex, identity and nickname
*/
export function info (instanceId: string, api: DeriveApi): (address?: AccountIndex | AccountId | Address | Uint8Array | string | null) => Observable<DeriveAccountInfo> {
return memo(instanceId, (address?: AccountIndex | AccountId | Address | Uint8Array | string | null): Observable<DeriveAccountInfo> =>
Expand Down
6 changes: 6 additions & 0 deletions packages/api-derive/src/staking/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeriveStakingAccount[]> {
Expand All @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions packages/typegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
183 changes: 183 additions & 0 deletions packages/typegen/src/metadataMd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string, StaticDef> = {
Expand Down Expand Up @@ -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<string, Derive[]>,
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<string, Derive[]>) {
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.<module>.<method>()` 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' });
Expand Down Expand Up @@ -642,6 +821,10 @@ async function mainPromise (): Promise<void> {
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 {
Expand Down
1 change: 1 addition & 0 deletions packages/typegen/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down