Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle

This commit is contained in:
2026-01-07 02:29:40 +03:00
commit d5f038faea
1383 changed files with 1088018 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# @pezkuwi/typegen
A collection of type generation scripts, allowing the input of metadata and definitions for the creation of TS outputs.
+52
View File
@@ -0,0 +1,52 @@
{
"author": "Jaco Greeff <jacogr@gmail.com>",
"bugs": "https://github.com/pezkuwichain/pezkuwi-api/issues",
"description": "Type generation scripts",
"engines": {
"node": ">=18"
},
"homepage": "https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/typegen#readme",
"license": "Apache-2.0",
"name": "@pezkuwi/typegen",
"repository": {
"directory": "packages/typegen",
"type": "git",
"url": "https://github.com/pezkuwichain/pezkuwi-api.git"
},
"sideEffects": [
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "16.5.6",
"main": "index.js",
"bin": {
"pezkuwi-types-chain-info": "./scripts/pezkuwi-types-chain-info.mjs",
"pezkuwi-types-from-chain": "./scripts/pezkuwi-types-from-chain.mjs",
"pezkuwi-types-from-defs": "./scripts/pezkuwi-types-from-defs.mjs",
"pezkuwi-types-internal-interfaces": "./scripts/pezkuwi-types-internal-interfaces.mjs",
"pezkuwi-types-internal-metadata": "./scripts/pezkuwi-types-internal-metadata.mjs"
},
"dependencies": {
"@pezkuwi/api": "16.5.4",
"@pezkuwi/api-augment": "16.5.4",
"@pezkuwi/api-derive": "16.5.4",
"@pezkuwi/rpc-augment": "16.5.4",
"@pezkuwi/rpc-provider": "16.5.4",
"@pezkuwi/types": "16.5.4",
"@pezkuwi/types-augment": "16.5.4",
"@pezkuwi/types-codec": "16.5.4",
"@pezkuwi/types-create": "16.5.4",
"@pezkuwi/types-support": "16.5.4",
"@pezkuwi/util": "^14.0.1",
"@pezkuwi/util-crypto": "^14.0.1",
"@pezkuwi/x-ws": "^14.0.1",
"comment-parser": "^1.4.1",
"handlebars": "^4.7.8",
"tslib": "^2.8.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/yargs": "^17.0.33"
}
}
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { main } from '@pezkuwi/typegen/extractChain';
main();
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { main } from '@pezkuwi/typegen/fromChain';
main();
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { main } from '@pezkuwi/typegen/fromDefs';
main();
@@ -0,0 +1,7 @@
#!/usr/bin/env node
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { main } from '@pezkuwi/typegen/interfacesTs';
main();
@@ -0,0 +1,7 @@
#!/usr/bin/env node
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { main } from '@pezkuwi/typegen/metadataMd';
main();
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import '@pezkuwi/api-augment';
import '@pezkuwi/rpc-augment';
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { packageInfo } from './packageInfo.js';
export { formatType } from './util/formatting.js';
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Connects to the local chain and outputs a re-usable calls-only chain definition in the form
// export default { chain: 'Development', genesisHash: '0x27b6d5e0f4fdce1c4d20b82406f193acacce0c19e0d2c0e7ca47725c2572a06a', ss58Format: 42, tokenDecimals: 0, tokenSymbol: 'UNIT'; metaCalls: 'bWV0...4AAA==' };
import process from 'node:process';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { ApiPromise, WsProvider } from '@pezkuwi/api';
/** @internal */
async function run (ws: string): Promise<void> {
const provider = new WsProvider(ws);
const api = await ApiPromise.create({ provider, throwOnConnect: true });
const [chain, props] = await Promise.all([
api.rpc.system.chain(),
api.rpc.system.properties()
]);
// output the chain info, for easy re-use
console.error(`// Generated via 'yarn run chain:info ${ws}'\n\nexport default {\n chain: '${chain.toString()}',\n genesisHash: '${api.genesisHash.toHex()}',\n specVersion: ${api.runtimeVersion.specVersion.toNumber()},\n ss58Format: ${props.ss58Format.unwrapOr(42).toString()},\n tokenDecimals: ${props.tokenDecimals.unwrapOr(0).toString()},\n tokenSymbol: '${props.tokenSymbol.unwrapOr('UNIT').toString()}',\n metaCalls: '${Buffer.from(api.runtimeMetadata.asCallsOnly.toU8a()).toString('base64')}'\n};`);
// show any missing types
api.runtimeMetadata.getUniqTypes(false);
}
interface ArgV { ws: string }
export function main (): void {
// retrieve and parse arguments - we do this globally, since this is a single command
const { ws } = yargs(hideBin(process.argv))
.usage('Usage: [options]')
.wrap(120)
.strict()
.options({
ws: {
default: 'ws://127.0.0.1:9944',
description: 'The API endpoint to connect to, e.g. wss://dicle-rpc.pezkuwi.io',
required: true,
type: 'string'
}
}).argv as ArgV;
run(ws)
.then((): void => {
process.exit(0);
})
.catch((error: Error) => {
console.error('FATAL:', error.message);
process.exit(-1);
});
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Definitions, DefinitionsTypes } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import fs from 'node:fs';
import path from 'node:path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { formatNumber, isHex } from '@pezkuwi/util';
import { generateDefaultConsts, generateDefaultErrors, generateDefaultEvents, generateDefaultQuery, generateDefaultRpc, generateDefaultRuntime, generateDefaultTx } from './generate/index.js';
import { assertDir, assertFile, getMetadataViaWs, HEADER, writeFile } from './util/index.js';
async function generate (metaHex: HexString, pkg: string | undefined, output: string, isStrict?: boolean): Promise<void> {
console.log(`Generating from metadata, ${formatNumber((metaHex.length - 2) / 2)} bytes`);
const outputPath = assertDir(path.join(process.cwd(), output));
let extraTypes: Record<string, any> = {};
let customLookupDefinitions: Definitions = { rpc: {}, types: {} };
if (pkg) {
try {
const defCont = await import(
assertFile(path.join(outputPath, 'definitions.ts'))
) as Record<string, any>;
extraTypes = {
[pkg]: defCont
};
} catch (error) {
console.error('ERROR: No custom definitions found:', (error as Error).message);
}
}
try {
const lookCont = await import(
assertFile(path.join(outputPath, 'lookup.ts'))
) as { default: DefinitionsTypes };
customLookupDefinitions = {
rpc: {},
types: lookCont.default
};
} catch (error) {
console.error('ERROR: No lookup definitions found:', (error as Error).message);
}
generateDefaultConsts(path.join(outputPath, 'augment-api-consts.ts'), metaHex, extraTypes, isStrict, customLookupDefinitions);
generateDefaultErrors(path.join(outputPath, 'augment-api-errors.ts'), metaHex, extraTypes, isStrict);
generateDefaultEvents(path.join(outputPath, 'augment-api-events.ts'), metaHex, extraTypes, isStrict, customLookupDefinitions);
generateDefaultQuery(path.join(outputPath, 'augment-api-query.ts'), metaHex, extraTypes, isStrict, customLookupDefinitions);
generateDefaultRpc(path.join(outputPath, 'augment-api-rpc.ts'), extraTypes);
generateDefaultRuntime(path.join(outputPath, 'augment-api-runtime.ts'), metaHex, extraTypes, isStrict, customLookupDefinitions);
generateDefaultTx(path.join(outputPath, 'augment-api-tx.ts'), metaHex, extraTypes, isStrict, customLookupDefinitions);
writeFile(path.join(outputPath, 'augment-api.ts'), (): string =>
[
HEADER('chain'),
...[
...['consts', 'errors', 'events', 'query', 'tx', 'rpc', 'runtime']
.filter((key) => !!key)
.map((key) => `./augment-api-${key}.js`)
].map((path) => `import '${path}';\n`)
].join('')
);
process.exit(0);
}
interface ArgV { endpoint: string; output: string; package?: string; strict?: boolean }
async function mainPromise (): Promise<void> {
const { endpoint, output, package: pkg, strict: isStrict } = yargs(hideBin(process.argv)).strict().options({
endpoint: {
description: 'The endpoint to connect to (e.g. wss://dicle-rpc.pezkuwi.io) or relative path to a file containing the JSON output of an RPC state_getMetadata call',
required: true,
type: 'string'
},
output: {
description: 'The target directory to write the data to',
required: true,
type: 'string'
},
package: {
description: 'Optional package in output location (for extra definitions)',
type: 'string'
},
strict: {
description: 'Turns on strict mode, no output of catch-all generic versions',
type: 'boolean'
}
}).argv as ArgV;
let metaHex: HexString;
if (endpoint.startsWith('wss://') || endpoint.startsWith('ws://')) {
metaHex = await getMetadataViaWs(endpoint);
} else {
metaHex = (
JSON.parse(
fs.readFileSync(assertFile(path.join(process.cwd(), endpoint)), 'utf-8')
) as { result: HexString }
).result;
if (!isHex(metaHex)) {
throw new Error('Invalid metadata file');
}
}
await generate(metaHex, pkg, output, isStrict);
}
export function main (): void {
mainPromise().catch((error) => {
console.error();
console.error(error);
console.error();
process.exit(1);
});
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@pezkuwi/util/types';
import fs from 'node:fs';
import path from 'node:path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as bizinikiwiDefs from '@pezkuwi/types/interfaces/definitions';
import { isHex } from '@pezkuwi/util';
import { generateDefaultLookup } from './generate/index.js';
import { generateInterfaceTypes } from './generate/interfaceRegistry.js';
import { generateTsDef } from './generate/tsDef.js';
import { assertDir, assertFile, getMetadataViaWs } from './util/index.js';
interface ArgV { input: string; package: string; endpoint?: string; }
async function mainPromise (): Promise<void> {
const { endpoint, input, package: pkg } = yargs(hideBin(process.argv)).strict().options({
endpoint: {
description: 'The endpoint to connect to (e.g. wss://dicle-rpc.pezkuwi.io) or relative path to a file containing the JSON output of an RPC state_getMetadata call',
type: 'string'
},
input: {
description: 'The directory to use for the user definitions',
required: true,
type: 'string'
},
package: {
description: 'The package name & path to use for the user types',
required: true,
type: 'string'
}
}).argv as ArgV;
const inputPath = assertDir(path.join(process.cwd(), input));
let userDefs: Record<string, any> = {};
try {
const defCont = await import(
assertFile(path.join(inputPath, 'definitions.ts'))
) as Record<string, any>;
userDefs = defCont;
} catch (error) {
console.error('ERROR: Unable to load user definitions:', (error as Error).message);
}
const userKeys = Object.keys(userDefs);
const filteredBase = Object
.entries(bizinikiwiDefs as Record<string, unknown>)
.filter(([key]) => {
if (userKeys.includes(key)) {
console.warn(`Override found for ${key} in user types, ignoring in @pezkuwi/types`);
return false;
}
return true;
})
.reduce((defs: Record<string, any>, [key, value]) => {
defs[key] = value;
return defs;
}, {});
const allDefs = {
'@pezkuwi/types/interfaces': filteredBase,
[pkg]: userDefs
};
generateTsDef(allDefs, inputPath, pkg);
generateInterfaceTypes(allDefs, path.join(inputPath, 'augment-types.ts'));
if (endpoint) {
let metaHex: HexString;
if (endpoint.startsWith('wss://') || endpoint.startsWith('ws://')) {
metaHex = await getMetadataViaWs(endpoint);
} else {
metaHex = (
JSON.parse(
fs.readFileSync(assertFile(path.join(process.cwd(), endpoint)), 'utf-8')
) as { result: HexString }
).result;
if (!isHex(metaHex)) {
throw new Error('Invalid metadata file');
}
}
generateDefaultLookup(inputPath, metaHex);
}
}
export function main (): void {
mainPromise().catch((error) => {
console.error();
console.error(error);
console.error();
process.exit(1);
});
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Metadata } from '@pezkuwi/types/metadata/Metadata';
import type { Definitions } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import * as defaultDefs from '@pezkuwi/types/interfaces/definitions';
import lookupDefinitions from '@pezkuwi/types-augment/lookup/definitions';
import { stringCamelCase } from '@pezkuwi/util';
import { compareName, createImports, formatType, initMeta, readTemplate, rebrandTypeName, setImports, writeFile } from '../util/index.js';
import { ignoreUnusedLookups } from './lookup.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
const generateForMetaTemplate = Handlebars.compile(readTemplate('consts'));
/** @internal */
function generateForMeta (meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
writeFile(dest, (): string => {
const allTypes = {
'@pezkuwi/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@pezkuwi/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const { lookup, pallets, registry } = meta.asLatest;
const usedTypes = new Set<string>([]);
const modules = pallets
.filter(({ constants }) => constants.length > 0)
.map(({ constants, name }) => {
if (!isStrict) {
setImports(allDefs, imports, ['Codec']);
}
const items = constants
.map(({ deprecationInfo, docs, name, type }) => {
const typeDef = lookup.getTypeDef(type);
const returnType = rebrandTypeName(typeDef.lookupName || '') || formatType(registry, allDefs, typeDef, imports);
if (!deprecationInfo.isNotDeprecated) {
const deprecationNotice = getDeprecationNotice(deprecationInfo, stringCamelCase(name), 'Constant');
const items = docs.length
? ['', deprecationNotice]
: [deprecationNotice];
docs.push(...items.map((text) => registry.createType('Text', text)));
}
// Add the type to the list of used types
if (!(imports.primitiveTypes[returnType])) {
usedTypes.add(returnType);
}
setImports(allDefs, imports, [returnType]);
return {
docs,
name: stringCamelCase(name),
type: returnType
};
})
.sort(compareName);
return {
items,
name: stringCamelCase(name)
};
})
.sort(compareName);
// filter out the unused lookup types from imports
ignoreUnusedLookups([...usedTypes], imports);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map<{ file: string; types: string[] }>((packagePath) => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedConst']
}
]
});
});
}
// Call `generateForMeta()` with current static metadata
/** @internal */
export function generateDefaultConsts (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
const { metadata } = initMeta(data, extraTypes);
return generateForMeta(metadata, dest, extraTypes, isStrict, customLookupDefinitions);
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { VariantDeprecationInfoV16 } from '@pezkuwi/types/interfaces';
import type { Metadata } from '@pezkuwi/types/metadata/Metadata';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import { stringCamelCase } from '@pezkuwi/util';
import { compareName, createImports, initMeta, readTemplate, writeFile } from '../util/index.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
const generateForMetaTemplate = Handlebars.compile(readTemplate('errors'));
/** @internal */
function generateForMeta (meta: Metadata, dest: string, isStrict: boolean): void {
writeFile(dest, (): string => {
const imports = createImports({});
const { lookup, pallets } = meta.asLatest;
const modules = pallets
.filter(({ errors }) => errors.isSome)
.map((data) => {
const name = data.name;
const errors = data.errors.unwrap();
const deprecationInfo = errors.deprecationInfo.toJSON();
return {
items: lookup.getSiType(errors.type).def.asVariant.variants
.map(({ docs, index, name }) => {
const rawStatus = deprecationInfo?.[index.toNumber()];
if (rawStatus) {
const deprecationVariantInfo: VariantDeprecationInfoV16 = meta.registry.createTypeUnsafe('VariantDeprecationInfoV16', [rawStatus]);
const deprecationNotice = getDeprecationNotice(deprecationVariantInfo, name.toString());
const notice = docs.length ? ['', deprecationNotice] : [deprecationNotice];
docs.push(...notice.map((text) => meta.registry.createType('Text', text)));
}
return {
docs,
name: name.toString()
};
})
.sort(compareName),
name: stringCamelCase(name)
};
})
.sort(compareName);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedError']
}
]
});
});
}
// Call `generateForMeta()` with current static metadata
/** @internal */
export function generateDefaultErrors (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false): void {
const { metadata } = initMeta(data, extraTypes);
return generateForMeta(metadata, dest, isStrict);
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { VariantDeprecationInfoV16 } from '@pezkuwi/types/interfaces';
import type { Metadata } from '@pezkuwi/types/metadata/Metadata';
import type { Definitions } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import * as defaultDefs from '@pezkuwi/types/interfaces/definitions';
import lookupDefinitions from '@pezkuwi/types-augment/lookup/definitions';
import { stringCamelCase } from '@pezkuwi/util';
import { compareName, createImports, formatType, initMeta, readTemplate, rebrandTypeName, setImports, writeFile } from '../util/index.js';
import { ignoreUnusedLookups } from './lookup.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
const generateForMetaTemplate = Handlebars.compile(readTemplate('events'));
// For babel itself we need some extra aliasing
// Also avoid reserved words to prevent generating invalid TS
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
const ALIAS = [
'symbol',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'new',
'null',
'return',
'static',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield'
];
/** @internal */
function generateForMeta (meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
writeFile(dest, (): string => {
const allTypes = {
'@pezkuwi/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@pezkuwi/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const { lookup, pallets, registry } = meta.asLatest;
const usedTypes = new Set<string>([]);
const modules = pallets
.filter(({ events }) => events.isSome)
.map((data) => {
const name = data.name;
const events = data.events.unwrap();
const deprecationInfo = events.deprecationInfo.toJSON();
return {
items: lookup.getSiType(events.type).def.asVariant.variants
.map(({ docs, fields, index, name }) => {
const rawStatus = deprecationInfo?.[index.toNumber()];
if (rawStatus) {
const deprecationVariantInfo: VariantDeprecationInfoV16 = meta.registry.createTypeUnsafe('VariantDeprecationInfoV16', [rawStatus]);
const deprecationNotice = getDeprecationNotice(deprecationVariantInfo, name.toString());
const notice = docs.length ? ['', deprecationNotice] : [deprecationNotice];
docs.push(...notice.map((text) => meta.registry.createType('Text', text)));
}
const args = fields
.map(({ type }) => lookup.getTypeDef(type))
.map((typeDef) => {
const arg = rebrandTypeName(typeDef.lookupName || '') || formatType(registry, allDefs, typeDef, imports);
// Add the type to the list of used types
if (!(imports.primitiveTypes[arg])) {
usedTypes.add(arg);
}
return arg;
});
const names = fields
.map(({ name }) => registry.lookup.sanitizeField(name)[0])
.filter((n): n is string => !!n);
setImports(allDefs, imports, args);
return {
docs,
name: name.toString(),
type: names.length !== 0 && names.length === args.length
? `[${names.map((n, i) => `${ALIAS.includes(n) ? `${n}_` : n}: ${args[i]}`).join(', ')}], { ${names.map((n, i) => `${n}: ${args[i]}`).join(', ')} }`
: `[${args.join(', ')}]`
};
})
.sort(compareName),
name: stringCamelCase(name)
};
})
.sort(compareName);
// filter out the unused lookup types from imports
ignoreUnusedLookups([...usedTypes], imports);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedEvent']
}
]
});
});
}
// Call `generateForMeta()` with current static metadata
/** @internal */
export function generateDefaultEvents (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
const { metadata } = initMeta(data, extraTypes);
return generateForMeta(metadata, dest, extraTypes, isStrict, customLookupDefinitions);
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { generateDefaultConsts } from './consts.js';
export { generateDefaultErrors } from './errors.js';
export { generateDefaultEvents } from './events.js';
export { generateDefaultInterface } from './interfaceRegistry.js';
export { generateDefaultLookup } from './lookup.js';
export { generateDefaultQuery } from './query.js';
export { generateDefaultRpc } from './rpc.js';
export { generateDefaultRuntime } from './runtime.js';
export { generateDefaultTsDef } from './tsDef.js';
export { generateDefaultTx } from './tx.js';
@@ -0,0 +1,85 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ModuleTypes } from '../util/imports.js';
import Handlebars from 'handlebars';
import { Json, Raw } from '@pezkuwi/types/codec';
import { TypeRegistry } from '@pezkuwi/types/create';
import * as defaultDefinitions from '@pezkuwi/types/interfaces/definitions';
import * as defaultPrimitives from '@pezkuwi/types/primitive';
import { createImports, readTemplate, setImports, writeFile } from '../util/index.js';
const primitiveClasses = {
...defaultPrimitives,
Json,
Raw
};
const generateInterfaceTypesTemplate = Handlebars.compile(readTemplate('interfaceRegistry'));
/** @internal */
export function generateInterfaceTypes (importDefinitions: Record<string, Record<string, ModuleTypes>>, dest: string): void {
const registry = new TypeRegistry();
writeFile(dest, (): string => {
Object.entries(importDefinitions).reduce((acc, def) => Object.assign(acc, def), {});
const imports = createImports(importDefinitions);
const definitions = imports.definitions;
const items: string[] = [];
// first we create imports for our known classes from the API
Object
.keys(primitiveClasses)
.filter((name) => !name.includes('Generic'))
.forEach((primitiveName): void => {
setImports(definitions, imports, [primitiveName]);
items.push(primitiveName);
});
const existingTypes: Record<string, boolean> = {};
// ensure we have everything registered since we will get the definition
// form the available types (so any unknown should show after this)
Object.values(definitions).forEach(({ types }) => {
registry.register(types as Record<string, string>);
});
// create imports for everything that we have available
Object.values(definitions).forEach(({ types }) => {
setImports(definitions, imports, Object.keys(types));
const uniqueTypes = Object.keys(types).filter((type) => !existingTypes[type]);
uniqueTypes.forEach((type): void => {
existingTypes[type] = true;
items.push(type);
});
});
return generateInterfaceTypesTemplate({
headerType: 'defs',
imports,
items: items.sort((a, b) => a.localeCompare(b)),
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]
});
});
}
// Generate `packages/types/src/interfaceRegistry.ts`, the registry of all interfaces
export function generateDefaultInterface (): void {
generateInterfaceTypes(
{ '@pezkuwi/types/interfaces': defaultDefinitions },
'packages/types-augment/src/registry/interfaces.ts'
);
}
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PortableType, SiLookupTypeId, SiPath, SiTypeParameter } from '@pezkuwi/types/interfaces';
import type { PortableRegistry } from '@pezkuwi/types/metadata';
import type { Registry } from '@pezkuwi/types/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import path from 'node:path';
import * as defaultDefinitions from '@pezkuwi/types/interfaces/definitions';
import staticAhDicle from '@pezkuwi/types-support/metadata/v15/asset-hub-dicle-hex';
import staticAhPezkuwi from '@pezkuwi/types-support/metadata/v15/asset-hub-pezkuwi-hex';
import staticBizinikiwi from '@pezkuwi/types-support/metadata/v15/bizinikiwi-hex';
import staticDicle from '@pezkuwi/types-support/metadata/v15/dicle-hex';
import staticPezkuwi from '@pezkuwi/types-support/metadata/v15/pezkuwi-hex';
import { isString, stringify } from '@pezkuwi/util';
import { createImports, exportInterface, initMeta, readTemplate, rebrandTypeName, type TypeImports, writeFile } from '../util/index.js';
import { typeEncoders } from './tsDef.js';
// Deep rebrand TypeDef including all nested sub types
// isTopLevel indicates whether this is a top-level type (should set name from lookupName) or a sub type (preserve name)
function deepRebrandTypeDef (typeDef: TypeDef, isTopLevel = true): TypeDef {
const rebrandedLookupName = typeDef.lookupName ? rebrandTypeName(typeDef.lookupName) : typeDef.lookupName;
const rebranded: TypeDef = {
...typeDef,
type: rebrandTypeName(typeDef.type),
// For top-level types: set name from lookupName (mimics original: typeDef.name = typeDef.lookupName)
// For sub types (enum variants, struct fields): preserve the original name (field/variant name)
name: isTopLevel ? (rebrandedLookupName || typeDef.name) : typeDef.name,
lookupName: rebrandedLookupName,
lookupNameRoot: typeDef.lookupNameRoot ? rebrandTypeName(typeDef.lookupNameRoot) : typeDef.lookupNameRoot
};
// Recursively rebrand sub types (mark as not top-level)
if (typeDef.sub) {
if (Array.isArray(typeDef.sub)) {
rebranded.sub = typeDef.sub.map((s) => deepRebrandTypeDef(s, false));
} else {
rebranded.sub = deepRebrandTypeDef(typeDef.sub, false);
}
}
return rebranded;
}
// Record<string, >
interface ParsedDef {
_set: Record<string, number>;
[key: string]: string | Record<string, string> | Record<string, number>;
}
const WITH_TYPEDEF = false;
const generateLookupDefsTmpl = Handlebars.compile(readTemplate('lookup/defs'));
const generateLookupDefsNamedTmpl = Handlebars.compile(readTemplate('lookup/defs-named'));
const generateLookupIndexTmpl = Handlebars.compile(readTemplate('lookup/index'));
const generateLookupTypesTmpl = Handlebars.compile(readTemplate('lookup/types'));
const generateRegistryTmpl = Handlebars.compile(readTemplate('interfaceRegistry'));
function generateParamType (registry: Registry, { name, type }: SiTypeParameter): string {
if (type.isSome) {
const link = registry.lookup.types[type.unwrap().toNumber()];
if (link.type.path.length) {
return generateTypeDocs(registry, null, link.type.path, link.type.params);
}
}
return name.toString();
}
function generateTypeDocs (registry: Registry, id: SiLookupTypeId | null, path: SiPath, params: SiTypeParameter[]): string {
return `${id ? `${registry.createLookupType(id)}${path.length ? ': ' : ''}` : ''}${path.map((p) => p.toString()).join('::')}${params.length ? `<${params.map((p) => generateParamType(registry, p)).join(', ')}>` : ''}`;
}
function formatObject (lines: string[]): string[] {
const max = lines.length - 1;
return [
'{',
...lines.map((l, index) =>
(l.endsWith(',') || l.endsWith('{') || index === max || lines[index + 1].endsWith('}') || lines[index + 1].endsWith('}'))
? l
: `${l},`
),
'}'
];
}
function expandSet (parsed: Record<string, number>): string[] {
return formatObject(
Object.entries(parsed).reduce<string[]>((all, [k, v]) => {
all.push(`${k}: ${v}`);
return all;
}, [])
);
}
function expandObject (parsed: ParsedDef): string[] {
if (parsed._set) {
return expandSet(parsed._set);
}
return formatObject(
Object.entries(parsed).reduce<string[]>((all, [k, v]) => {
const inner = isString(v)
? expandType(v)
: Array.isArray(v)
? [`[${(v as string[]).map((e) => `'${e}'`).join(', ')}]`]
: expandObject(v as ParsedDef);
inner.forEach((l, index): void => {
all.push(`${
index === 0
? `${k}: ${l}`
: `${l}`
}`);
});
return all;
}, [])
);
}
function expandType (encoded: string): string[] {
if (!encoded.startsWith('{')) {
return [`'${rebrandTypeName(encoded)}'`];
}
return expandObject(JSON.parse(encoded) as ParsedDef);
}
function expandDefToString ({ lookupNameRoot, type }: TypeDef, indent: number): string {
if (lookupNameRoot) {
return `'${rebrandTypeName(lookupNameRoot)}'`;
}
const lines = expandType(type);
let inc = 0;
return lines.map((l, index) => {
let r: string;
if (l.endsWith('{')) {
r = index === 0
? l
: `${' '.padStart(indent + inc)}${l}`;
inc += 2;
} else {
if (l.endsWith('},') || l.endsWith('}')) {
inc -= 2;
}
r = index === 0
? l
: `${' '.padStart(indent + inc)}${l}`;
}
return r;
}).join('\n');
}
function getFilteredTypes (lookup: PortableRegistry, exclude: string[] = []): [PortableType, TypeDef][] {
const named = lookup.types.filter(({ id }) => !!lookup.getTypeDef(id).lookupName);
const names = named.map(({ id }) => lookup.getName(id));
return named
.filter((_, index) =>
!names.some((n, iindex) =>
index > iindex &&
n === names[index]
)
)
.map((p): [PortableType, TypeDef] => [p, lookup.getTypeDef(p.id)])
.filter(([, typeDef]) => !exclude.includes(typeDef.lookupName || '<invalid>'));
}
function generateLookupDefs (registry: Registry, filtered: [PortableType, TypeDef][], destDir: string, subPath?: string): void {
writeFile(path.join(destDir, `${subPath || 'definitions'}.ts`), (): string => {
const all = filtered.map(([{ id, type: { params, path } }, typeDef]) => {
const typeLookup = registry.createLookupType(id);
const def = expandDefToString(typeDef, subPath ? 2 : 4);
return {
docs: [
generateTypeDocs(registry, id, path, params),
WITH_TYPEDEF
? `@typeDef ${stringify(typeDef)}`
: null
].filter((d): d is string => !!d),
type: { def, typeLookup, typeName: typeDef.lookupName ? rebrandTypeName(typeDef.lookupName) : undefined }
};
});
const max = all.length - 1;
return (subPath ? generateLookupDefsNamedTmpl : generateLookupDefsTmpl)({
defs: all.map(({ docs, type }, i) => {
const { def, typeLookup, typeName } = type;
return {
defs: [
[typeName || typeLookup, `${def}${i !== max ? ',' : ''}`]
].map(([n, t]) => `${n}: ${t}`),
docs
};
}),
headerType: 'defs'
});
});
}
function generateLookupTypes (registry: Registry, filtered: [PortableType, TypeDef][], destDir: string, subPath?: string): void {
const imports = {
...createImports(
{ '@pezkuwi/types/interfaces': defaultDefinitions },
{ types: {} }
),
interfaces: []
};
const items = filtered
.map(([, typeDef]) => {
// Deep rebrand the type names (including nested sub types) before generating interfaces
const rebranded = deepRebrandTypeDef(typeDef);
return rebranded.lookupNameRoot && rebranded.lookupName
? exportInterface(rebranded.lookupIndex, rebranded.lookupName, rebranded.lookupNameRoot)
: typeEncoders[rebranded.info](registry, imports.definitions, rebranded, imports);
})
.filter((t): t is string => !!t)
.map((t) => t.replace(/\nexport /, '\n'));
writeFile(path.join(destDir, `types${subPath ? `-${subPath}` : ''}.ts`), () => generateLookupTypesTmpl({
headerType: 'defs',
imports,
items: items.map((l) =>
l
.split('\n')
.map((l) => l.length ? ` ${l}` : '')
.join('\n')
),
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath,
types: Object.keys(imports.localTypes[packagePath])
}))
]
}), true);
writeFile(path.join(destDir, 'index.ts'), () => generateLookupIndexTmpl({ headerType: 'defs' }), true);
}
function generateRegistry (_registry: Registry, filtered: [PortableType, TypeDef][], destDir: string, subPath: string): void {
writeFile(path.join(destDir, `${subPath}.ts`), (): string => {
const items = filtered
.map(([, { lookupName }]) => lookupName ? rebrandTypeName(lookupName) : lookupName)
.filter((n): n is string => !!n)
.sort()
.reduce((all: string[], n) => all.includes(n) ? all : all.concat(n), []);
const imports = createImports({}, { types: {} });
imports.lookupTypes = items.reduce((all, n) => ({ ...all, [n]: true }), {});
return generateRegistryTmpl({
headerType: 'defs',
imports,
items,
types: []
});
}, true);
}
function generateLookup (destDir: string, entries: [string, HexString][]): void {
entries.reduce<string[]>((exclude, [subPath, staticMeta]): string[] => {
const { lookup, registry } = initMeta(staticMeta).metadata.asLatest;
const filtered = getFilteredTypes(lookup, exclude);
generateLookupDefs(registry, filtered, destDir, subPath);
generateLookupTypes(registry, filtered, destDir, subPath);
generateRegistry(registry, filtered, destDir, subPath === 'lookup' ? 'registry' : `../registry/${subPath}`);
return exclude.concat(
...filtered
.map(([, typeDef]) => typeDef.lookupName)
.filter((n): n is string => !!n)
);
}, []);
}
// Generate `packages/types/src/lookup/*s`, the registry of all lookup types
export function generateDefaultLookup (destDir = 'packages/types-augment/src/lookup', staticData?: HexString): void {
generateLookup(
destDir,
staticData
? [['lookup', staticData]]
: [
['bizinikiwi', staticBizinikiwi],
['pezkuwi', staticPezkuwi],
['dicle', staticDicle],
['assetHubPezkuwi', staticAhPezkuwi],
['assetHubDicle', staticAhDicle]
]
);
}
// Based on a list of types, it filters out the lookup types that are not needed.
export function ignoreUnusedLookups (usedTypes: string[], imports: TypeImports) {
const usedStringified = usedTypes.toString();
const [lookupKey, typeDefinitions] = Object.entries(imports.localTypes).find(([typeModule, _]) => typeModule.includes('/lookup')) || ['', {}];
Object.keys(typeDefinitions).forEach((typeDef) => {
if (!(usedStringified.includes(typeDef))) {
delete (imports.localTypes[lookupKey])[typeDef];
}
});
}
+169
View File
@@ -0,0 +1,169 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { StorageEntryMetadataLatest } from '@pezkuwi/types/interfaces';
import type { Metadata, PortableRegistry } from '@pezkuwi/types/metadata';
import type { Definitions, Registry } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import type { ModuleTypes, TypeImports } from '../util/imports.js';
import Handlebars from 'handlebars';
import * as defaultDefs from '@pezkuwi/types/interfaces/definitions';
import { unwrapStorageSi } from '@pezkuwi/types/util';
import lookupDefinitions from '@pezkuwi/types-augment/lookup/definitions';
import { stringCamelCase } from '@pezkuwi/util';
import { compareName, createImports, formatType, getSimilarTypes, initMeta, readTemplate, rebrandTypeName, setImports, writeFile } from '../util/index.js';
import { ignoreUnusedLookups } from './lookup.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
const generateForMetaTemplate = Handlebars.compile(readTemplate('query'));
// From a storage entry metadata, we return [args, returnType]
/** @internal */
function entrySignature (lookup: PortableRegistry, allDefs: Record<string, ModuleTypes>, registry: Registry, section: string, storageEntry: StorageEntryMetadataLatest, imports: TypeImports): [boolean, string, string, string] {
try {
const outputType = lookup.getTypeDef(unwrapStorageSi(storageEntry.type));
if (storageEntry.type.isPlain) {
const typeDef = lookup.getTypeDef(storageEntry.type.asPlain);
setImports(allDefs, imports, [
rebrandTypeName(typeDef.lookupName || typeDef.type),
storageEntry.modifier.isOptional
? 'Option'
: null
]);
return [storageEntry.modifier.isOptional, '', '', formatType(registry, allDefs, outputType, imports)];
} else if (storageEntry.type.isMap) {
const { hashers, key, value } = storageEntry.type.asMap;
const keyDefs = hashers.length === 1
? [lookup.getTypeDef(key)]
: lookup.getSiType(key).def.asTuple.map((k) => lookup.getTypeDef(k));
const similarTypes = keyDefs.map((k) => getSimilarTypes(registry, allDefs, k.lookupName || k.type, imports));
const keyTypes = similarTypes.map((t) => t.join(' | '));
const defValue = lookup.getTypeDef(value);
setImports(allDefs, imports, [
...similarTypes.reduce<string[]>((all, t) => all.concat(t), []),
storageEntry.modifier.isOptional
? 'Option'
: null,
rebrandTypeName(defValue.lookupName || defValue.type)
]);
return [
storageEntry.modifier.isOptional,
keyDefs.map((k) => formatType(registry, allDefs, k.lookupName || k.type, imports)).join(', '),
keyTypes.map((t, i) => `arg${keyTypes.length === 1 ? '' : (i + 1)}: ${t}`).join(', '),
rebrandTypeName(outputType.lookupName || '') || formatType(registry, allDefs, outputType, imports)
];
}
throw new Error(`Expected Plain or Map type, found ${storageEntry.type.type}`);
} catch (error) {
throw new Error(`entrySignature: Cannot create signature for query ${section}.${storageEntry.name.toString()}:: ${(error as Error).message}`);
}
}
/** @internal */
function generateForMeta (registry: Registry, meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
writeFile(dest, (): string => {
const allTypes: ExtraTypes = {
'@pezkuwi/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@pezkuwi/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const { lookup, pallets } = meta.asLatest;
const usedTypes = new Set<string>([]);
const modules = pallets
.filter(({ storage }) => storage.isSome)
.map(({ name, storage }) => {
const items = storage.unwrap().items
.map((storageEntry) => {
const { deprecationInfo, docs, name } = storageEntry;
const [isOptional, args, params, _returnType] = entrySignature(lookup, allDefs, registry, name.toString(), storageEntry, imports);
if (!deprecationInfo.isNotDeprecated) {
const deprecationNotice = getDeprecationNotice(deprecationInfo, stringCamelCase(name));
const items = docs.length
? ['', deprecationNotice]
: [deprecationNotice];
docs.push(...items.map((text) => registry.createType('Text', text)));
}
// Add the type and args to the list of used types
if (!(imports.primitiveTypes[_returnType])) {
usedTypes.add(_returnType);
}
if (!(imports.primitiveTypes[args])) {
usedTypes.add(args);
}
const returnType = isOptional
? `Option<${_returnType}>`
: _returnType;
return {
args,
docs,
entryType: 'AugmentedQuery',
name: stringCamelCase(storageEntry.name),
params,
returnType
};
})
.sort(compareName);
return {
items,
name: stringCamelCase(name)
};
})
.sort(compareName);
imports.typesTypes['Observable'] = true;
// filter out the unused lookup types from imports
ignoreUnusedLookups([...usedTypes], imports);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedQuery', 'QueryableStorageEntry']
}
]
});
});
}
// Call `generateForMeta()` with current static metadata
/** @internal */
export function generateDefaultQuery (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
const { metadata, registry } = initMeta(data, extraTypes);
return generateForMeta(registry, metadata, dest, extraTypes, isStrict, customLookupDefinitions);
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TypeRegistry } from '@pezkuwi/types/create';
import type { Definitions } from '@pezkuwi/types/types';
import type { ExtraTypes } from './types.js';
import Handlebars from 'handlebars';
import * as defaultDefinitions from '@pezkuwi/types/interfaces/definitions';
import staticBizinikiwi from '@pezkuwi/types-support/metadata/static-bizinikiwi';
import { createImports, formatType, getSimilarTypes, initMeta, readTemplate, setImports, writeFile } from '../util/index.js';
interface ItemDef {
args: string;
docs: string[];
generic: string | undefined;
name: string;
type: string | undefined;
}
interface ModuleDef {
items: ItemDef[];
name: string;
}
const StorageKeyType = 'StorageKey | string | Uint8Array | any';
const generateRpcTypesTemplate = Handlebars.compile(readTemplate('rpc'));
/** @internal */
export function generateRpcTypes (registry: TypeRegistry, importDefinitions: Record<string, Definitions>, dest: string, extraTypes: ExtraTypes): void {
writeFile(dest, (): string => {
const allTypes: ExtraTypes = { '@pezkuwi/types/interfaces': importDefinitions, ...extraTypes };
const imports = createImports(allTypes);
const definitions = imports.definitions as Record<string, Definitions>;
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const rpcKeys = Object
.keys(definitions)
.filter((key) => Object.keys(definitions[key].rpc || {}).length !== 0)
.sort();
const additional: Record<string, ModuleDef> = {};
const modules = rpcKeys.map((sectionFullName) => {
const rpc = definitions[sectionFullName].rpc || {};
const section = sectionFullName.split('/').pop();
const allMethods = Object.keys(rpc).sort().map((methodName) => {
const def = rpc[methodName];
let args;
let type;
let generic;
// These are too hard to type with generics, do manual overrides
if (section === 'state') {
setImports(allDefs, imports, ['Codec', 'Hash', 'StorageKey', 'Vec']);
if (methodName === 'getStorage') {
generic = 'T = Codec';
args = [`key: ${StorageKeyType}, block?: Hash | Uint8Array | string`];
type = 'T';
} else if (methodName === 'queryStorage') {
generic = 'T = Codec[]';
args = [`keys: Vec<StorageKey> | (${StorageKeyType})[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string`];
type = '[Hash, T][]';
} else if (methodName === 'queryStorageAt') {
generic = 'T = Codec[]';
args = [`keys: Vec<StorageKey> | (${StorageKeyType})[], at?: Hash | Uint8Array | string`];
type = 'T';
} else if (methodName === 'subscribeStorage') {
generic = 'T = Codec[]';
args = [`keys?: Vec<StorageKey> | (${StorageKeyType})[]`];
type = 'T';
}
}
if (args === undefined) {
setImports(allDefs, imports, [def.type]);
args = def.params.map((param) => {
const similarTypes = getSimilarTypes(registry, definitions, param.type, imports);
setImports(allDefs, imports, [param.type, ...similarTypes]);
return `${param.name}${param.isOptional ? '?' : ''}: ${similarTypes.join(' | ')}`;
});
type = formatType(registry, allDefs, def.type, imports);
generic = '';
}
const item = {
args: args.join(', '),
docs: def.deprecated
? [`@deprecated ${def.deprecated}`, def.description]
: [def.description],
generic,
name: methodName,
type
};
if (def.aliasSection) {
if (!additional[def.aliasSection]) {
additional[def.aliasSection] = {
items: [],
name: def.aliasSection
};
}
additional[def.aliasSection].items.push(item);
return null;
}
return item;
}).filter((item): item is ItemDef => !!item);
return {
items: allMethods,
name: section || 'unknown'
};
}).concat(...Object.values(additional)).sort((a, b) => a.name.localeCompare(b.name));
imports.typesTypes['Observable'] = true;
return generateRpcTypesTemplate({
headerType: 'chain',
imports,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/rpc-core/types',
types: ['AugmentedRpc']
}
]
});
});
}
export function generateDefaultRpc (dest = 'packages/rpc-augment/src/augment/jsonrpc.ts', extraTypes: ExtraTypes = {}): void {
const { registry } = initMeta(staticBizinikiwi, extraTypes);
generateRpcTypes(
registry,
defaultDefinitions,
dest,
extraTypes
);
}
+293
View File
@@ -0,0 +1,293 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RuntimeApiMethodMetadataV16, SiLookupTypeId } from '@pezkuwi/types/interfaces';
import type { Metadata } from '@pezkuwi/types/metadata/Metadata';
import type { DefinitionCall, DefinitionCallNamed, Definitions, DefinitionsCall, Registry } from '@pezkuwi/types/types';
import type { Vec } from '@pezkuwi/types-codec';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import * as defaultDefs from '@pezkuwi/types/interfaces/definitions';
import lookupDefinitions from '@pezkuwi/types-augment/lookup/definitions';
import { objectSpread, stringCamelCase } from '@pezkuwi/util';
import { blake2AsHex } from '@pezkuwi/util-crypto';
import { createImports, formatType, getSimilarTypes, initMeta, readTemplate, rebrandTypeName, setImports, writeFile } from '../util/index.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
type Apis = [HexString, number][];
const generateCallsTypesTemplate = Handlebars.compile(readTemplate('calls'));
// This works similar to the PATHS_ALIAS set from the PortableRegistry
const aliases: Record<string, string> = {
AssetHubZagrosRuntimeRuntimeCall: 'RuntimeCall',
AssetHubPezkuwiRuntimeRuntimeCall: 'RuntimeCall',
AssetHubDicleRuntimeOriginCaller: 'OriginCaller',
AssetHubDicleRuntimeRuntimeCall: 'RuntimeCall',
DicleRuntimeConstantsProxyProxyType: 'ProxyType',
KitchensinkRuntimeRuntimeCall: 'RuntimeCall',
KitchensinkRuntimeRuntimeParametersKey: 'RuntimeParametersKey',
OpaqueValue: 'Bytes',
PezkuwiTeyrchainPrimitivesPrimitivesId: 'ParaId',
PezkuwiTeyrchainPrimitivesPrimitivesValidationCodeHash: 'ValidationCodeHash',
PezkuwiPrimitivesV7SlashingOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
PezkuwiPrimitivesV8SlashingOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
PezkuwiRuntimeRuntimeCall: 'RuntimeCall',
PrimitiveTypesH160: 'H160',
PrimitiveTypesH256: 'H256',
PrimitiveTypesU256: 'U256',
PezspConsensusBabeOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
PezspConsensusSlotsSlot: 'Slot',
PezspConsensusSlotsSlotDuration: 'SlotDuration',
PezspCoreCryptoAccountId32: 'AccountId32',
PezspCoreOpaqueMetadata: 'OpaqueMetadata',
PezspRuntimeOpaqueValue: 'Bytes',
PezspRuntimeUncheckedExtrinsic: 'Extrinsic',
StagingDicleRuntimeOriginCaller: 'OriginCaller',
StagingDicleRuntimeRuntimeCall: 'RuntimeCall',
StagingDicleRuntimeRuntimeParameters: 'RuntimeParameters',
StagingDicleRuntimeRuntimeParametersKey: 'RuntimeParametersKey',
StagingZagrosRuntimeRuntimeCall: 'RuntimeCall'
};
const getTypesViaAlias = (registry: Registry, id: SiLookupTypeId) => {
const rawTypeName = registry.lookup.getName(id) || registry.lookup.getTypeDef(id).type;
const typeName = rebrandTypeName(rawTypeName);
if (aliases[typeName]) {
return aliases[typeName];
}
return typeName;
};
/** @internal */
function getMethods (registry: Registry, methods: Vec<RuntimeApiMethodMetadataV16>) {
const result: Record<string, DefinitionCall> = {};
methods.forEach((m) => {
const { deprecationInfo, docs, inputs, name, output } = m;
let description = docs.map((d) => d.toString()).join();
if (!deprecationInfo.isNotDeprecated) {
const deprecationNotice = getDeprecationNotice(deprecationInfo, stringCamelCase(name));
const notice = description.length ? `\n * ${deprecationNotice}` : ` * ${deprecationNotice}`;
description += notice;
}
result[name.toString()] = {
description,
params: inputs.map(({ name, type }) => {
return { name: name.toString(), type: getTypesViaAlias(registry, type) };
}),
type: getTypesViaAlias(registry, output)
};
});
return result;
}
/** @internal */
function getRuntimeDefViaMetadata (registry: Registry) {
const result: DefinitionsCall = {};
const { apis } = registry.metadata;
for (let i = 0, count = apis.length; i < count; i++) {
const { methods, name } = apis[i];
result[name.toString()] = [{
methods: getMethods(registry, methods),
// We set the version to 0 here since it will not be relevant when we are grabbing the runtime apis
// from the Metadata.
version: 0
}];
}
return Object.entries(result);
}
/** @internal */
function getDefs (apis: Apis | null, defs: Record<string, Definitions>, registry: Registry): Record<string, Record<string, DefinitionCallNamed>> {
const named: Record<string, Record<string, DefinitionCallNamed>> = {};
const all = Object.values(defs);
const isApiInMetadata = registry.metadata.apis.length > 0;
if (isApiInMetadata) {
const sections = getRuntimeDefViaMetadata(registry);
for (let j = 0, jcount = sections.length; j < jcount; j++) {
const [_section, secs] = sections[j];
const sec = secs[0];
const sectionHash = blake2AsHex(_section, 64);
const section = stringCamelCase(_section);
const methods = Object.entries(sec.methods);
if (!named[section]) {
named[section] = {};
}
for (let m = 0, mcount = methods.length; m < mcount; m++) {
const [_method, def] = methods[m];
const method = stringCamelCase(_method);
named[section][method] = objectSpread({ method, name: `${_section}_${_method}`, section, sectionHash }, def);
}
}
} else {
for (let j = 0, jcount = all.length; j < jcount; j++) {
const set = all[j].runtime;
if (set) {
const sections = Object.entries(set);
for (let i = 0, scount = sections.length; i < scount; i++) {
const [_section, sec] = sections[i];
const sectionHash = blake2AsHex(_section, 64);
const api = apis?.find(([h]) => h === sectionHash);
if (api) {
const ver = sec.find(({ version }) => version === api[1]);
if (ver) {
const methods = Object.entries(ver.methods);
const mcount = methods.length;
if (mcount) {
const section = stringCamelCase(_section);
if (!named[section]) {
named[section] = {};
}
for (let m = 0; m < mcount; m++) {
const [_method, def] = methods[m];
const method = stringCamelCase(_method);
named[section][method] = objectSpread({ method, name: `${_section}_${method}`, section, sectionHash, version: ver.version }, def);
}
}
} else {
console.warn(`Unable to find matching version for runtime ${_section}, expected ${api[1]}`);
}
}
}
}
}
}
return named;
}
/** @internal */
export function generateCallTypes (registry: Registry, meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
writeFile(dest, (): string => {
const allTypes: ExtraTypes = {
'@pezkuwi/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@pezkuwi/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
// find the system.Version in metadata
let apis: Apis | null = null;
const sysp = meta.asLatest.pallets.find(({ name }) => name.eq('System'));
if (sysp) {
const verc = sysp.constants.find(({ name }) => name.eq('Version'));
if (verc) {
apis = registry.createType('RuntimeVersion', verc.value).apis.map(([k, v]): [HexString, number] => [k.toHex(), v.toNumber()]);
} else {
console.error('Unable to find System.Version pallet, skipping API extraction');
}
} else {
console.error('Unable to find System pallet, skipping API extraction');
}
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const definitions = getDefs(apis, imports.definitions as Record<string, Definitions>, registry);
const callKeys = Object.keys(definitions);
const modules = callKeys.map((section) => {
const calls = definitions[section];
const allMethods = Object.keys(calls).sort().map((methodName) => {
const def = calls[methodName];
setImports(allDefs, imports, [def.type]);
const args = def.params.map((param) => {
const similarTypes = getSimilarTypes(registry, imports.definitions, param.type, imports);
setImports(allDefs, imports, [param.type, ...similarTypes]);
return `${param.name}: ${similarTypes.join(' | ')}`;
});
return {
args: args.join(', '),
docs: [def.description],
name: methodName,
sectionHash: def.sectionHash,
sectionName: def.section,
sectionVersion: def.version,
type: formatType(registry, allDefs, def.type, imports)
};
}).sort((a, b) => a.name.localeCompare(b.name));
return {
items: allMethods,
name: section || 'unknown',
sectionHash: allMethods.length && allMethods[0].sectionHash,
sectionName: allMethods.length && allMethods[0].sectionName,
sectionVersion: allMethods.length && allMethods[0].sectionVersion
};
}).filter(({ items }) => items.length).sort((a, b) => a.name.localeCompare(b.name));
if (modules.length) {
imports.typesTypes['Observable'] = true;
}
return generateCallsTypesTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedCall', 'DecoratedCallBase']
}
]
});
});
}
export function generateDefaultRuntime (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
const { metadata, registry } = initMeta(data, extraTypes);
generateCallTypes(
registry,
metadata,
dest,
extraTypes,
isStrict,
customLookupDefinitions
);
}
+321
View File
@@ -0,0 +1,321 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registry } from '@pezkuwi/types/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import type { ModuleTypes } from '../util/imports.js';
import type { TypeImports } from '../util/index.js';
import Handlebars from 'handlebars';
import path from 'node:path';
import { TypeRegistry } from '@pezkuwi/types/create';
import * as defaultDefinitions from '@pezkuwi/types/interfaces/definitions';
import { getTypeDef, TypeDefInfo } from '@pezkuwi/types-create';
import { assert, isString, stringify, stringPascalCase } from '@pezkuwi/util';
import { createImports, exportInterface, formatType, readTemplate, setImports, writeFile } from '../util/index.js';
interface Imports extends TypeImports {
interfaces: [string, string][];
}
const generateTsDefIndexTemplate = Handlebars.compile(readTemplate('tsDef/index'));
const generateTsDefModuleTypesTemplate = Handlebars.compile(readTemplate('tsDef/moduleTypes'));
const generateTsDefTypesTemplate = Handlebars.compile(readTemplate('tsDef/types'));
// helper to generate a `readonly <Name>: <Type>;` getter
/** @internal */
export function createGetter (definitions: Record<string, ModuleTypes>, name = '', type: string, imports: TypeImports): string {
setImports(definitions, imports, [type]);
return ` readonly ${name}: ${type};\n`;
}
/** @internal */
function errorUnhandled (_: Registry, _definitions: Record<string, ModuleTypes>, def: TypeDef, _imports: TypeImports): string {
throw new Error(`Generate: ${def.name || ''}: Unhandled type ${TypeDefInfo[def.info]}`);
}
/** @internal */
function tsExport (registry: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports): string {
return exportInterface(def.lookupIndex, def.name, formatType(registry, definitions, def, imports, false));
}
/** @internal */
function tsEnum (registry: Registry, definitions: Record<string, ModuleTypes>, { lookupIndex, name: enumName, sub }: TypeDef, imports: TypeImports, withShortcut = false): string {
setImports(definitions, imports, ['Enum']);
const indent = withShortcut ? ' ' : '';
const named = (sub as TypeDef[]).filter(({ name }) => !!name && !name.startsWith('__Unused'));
const keys = named.map((def): string => {
const { info, lookupName, name = '', sub, type } = def;
const getter = stringPascalCase(name.replace(' ', '_'));
const isComplex = [TypeDefInfo.Option, TypeDefInfo.Range, TypeDefInfo.RangeInclusive, TypeDefInfo.Result, TypeDefInfo.Struct, TypeDefInfo.Tuple, TypeDefInfo.Vec, TypeDefInfo.VecFixed].includes(info);
let extractedLookupName;
// When the parent type does not have a lookupName, and the sub type is the same
// type as the parent we can take the lookupName from the sub.
// This is specific to `StagingXcmV4Junction`.
// see: https://github.com/pezkuwichain/pezkuwi-api/pull/5812
if (sub && !Array.isArray(sub) && type.includes(`${sub.type};`)) {
if (sub.lookupName === 'StagingXcmV4Junction') {
extractedLookupName = sub.lookupName;
} else if (sub.lookupName === 'StagingXcmV5Junction') {
extractedLookupName = `Vec<${sub.lookupName}>`;
}
}
const asGetter = type === 'Null' || info === TypeDefInfo.DoNotConstruct
? ''
: createGetter(definitions, `as${getter}`, lookupName || extractedLookupName || (isComplex ? formatType(registry, definitions, info === TypeDefInfo.Struct ? def : type, imports, withShortcut) : type), imports);
const isGetter = info === TypeDefInfo.DoNotConstruct
? ''
: createGetter(definitions, `is${getter}`, 'boolean', imports);
switch (info) {
case TypeDefInfo.Compact:
case TypeDefInfo.Plain:
case TypeDefInfo.Range:
case TypeDefInfo.RangeInclusive:
case TypeDefInfo.Result:
case TypeDefInfo.Si:
case TypeDefInfo.Struct:
case TypeDefInfo.Tuple:
case TypeDefInfo.Vec:
case TypeDefInfo.BTreeMap:
case TypeDefInfo.BTreeSet:
case TypeDefInfo.Option:
case TypeDefInfo.VecFixed:
case TypeDefInfo.WrapperKeepOpaque:
case TypeDefInfo.WrapperOpaque:
return `${indent}${isGetter}${indent}${asGetter}`;
case TypeDefInfo.DoNotConstruct:
case TypeDefInfo.Null:
return `${indent}${isGetter}`;
default:
throw new Error(`Enum: ${enumName || 'undefined'}: Unhandled type ${TypeDefInfo[info]}, ${stringify(def)}`);
}
});
return exportInterface(lookupIndex, enumName, 'Enum', `${keys.join('')} ${indent}readonly type: ${named.map(({ name = '' }) => `'${stringPascalCase(name.replace(' ', '_'))}'`).join(' | ')};\n`, withShortcut);
}
function tsInt (_: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports, type: 'Int' | 'UInt' = 'Int'): string {
setImports(definitions, imports, [type]);
return exportInterface(def.lookupIndex, def.name, type);
}
/** @internal */
function tsNull (_registry: Registry, definitions: Record<string, ModuleTypes>, { lookupIndex = -1, name }: TypeDef, imports: TypeImports): string {
setImports(definitions, imports, ['Null']);
// * @description extends [[${base}]]
const doc = `/** @name ${name || ''}${lookupIndex !== -1 ? ` (${lookupIndex})` : ''} */\n`;
return `${doc}export type ${name || ''} = Null;`;
}
/** @internal */
function tsResultGetter (registry: Registry, definitions: Record<string, ModuleTypes>, resultName = '', getter: 'Ok' | 'Err', def: TypeDef, imports: TypeImports): string {
const { info, lookupName, type } = def;
const asGetter = type === 'Null'
? ''
: createGetter(definitions, `as${getter}`, lookupName || (info === TypeDefInfo.Tuple ? formatType(registry, definitions, def, imports, false) : type), imports);
const isGetter = createGetter(definitions, `is${getter}`, 'boolean', imports);
switch (info) {
case TypeDefInfo.Option:
case TypeDefInfo.Plain:
case TypeDefInfo.Si:
case TypeDefInfo.Tuple:
case TypeDefInfo.Vec:
case TypeDefInfo.BTreeMap:
case TypeDefInfo.BTreeSet:
case TypeDefInfo.WrapperKeepOpaque:
case TypeDefInfo.WrapperOpaque:
return `${isGetter}${asGetter}`;
case TypeDefInfo.Null:
return `${isGetter}`;
default:
throw new Error(`Result: ${resultName}: Unhandled type ${TypeDefInfo[info]}, ${stringify(def)}`);
}
}
/** @internal */
function tsResult (registry: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports): string {
const [okDef, errorDef] = (def.sub as TypeDef[]);
const inner = [
tsResultGetter(registry, definitions, def.name, 'Err', errorDef, imports),
tsResultGetter(registry, definitions, def.name, 'Ok', okDef, imports)
].join('');
setImports(definitions, imports, [def.type]);
const fmtType = def.lookupName && def.name !== def.lookupName
? def.lookupName
: formatType(registry, definitions, def, imports, false);
return exportInterface(def.lookupIndex, def.name, fmtType, inner);
}
/** @internal */
function tsSi (_registry: Registry, _definitions: Record<string, ModuleTypes>, typeDef: TypeDef, _imports: TypeImports): string {
// FIXME
return `// SI: ${stringify(typeDef)}`;
}
/** @internal */
function tsSet (_: Registry, definitions: Record<string, ModuleTypes>, { lookupIndex, name: setName, sub }: TypeDef, imports: TypeImports): string {
setImports(definitions, imports, ['Set']);
const types = (sub as TypeDef[]).map(({ name }): string => {
assert(name, 'Invalid TypeDef found, no name specified');
return createGetter(definitions, `is${name}`, 'boolean', imports);
});
return exportInterface(lookupIndex, setName, 'Set', types.join(''));
}
/** @internal */
function tsStruct (registry: Registry, definitions: Record<string, ModuleTypes>, { lookupIndex, name: structName, sub }: TypeDef, imports: TypeImports): string {
setImports(definitions, imports, ['Struct']);
const keys = (sub as TypeDef[]).map((def): string => {
const fmtType = def.lookupName && def.name !== def.lookupName
? def.lookupName
: def.info === TypeDefInfo.Enum
? `${tsEnum(registry, definitions, def, imports, true)} & Enum`
: formatType(registry, definitions, def, imports, false);
return createGetter(definitions, def.name, fmtType, imports);
});
return exportInterface(lookupIndex, structName, 'Struct', keys.join(''));
}
/** @internal */
function tsUInt (registry: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports): string {
return tsInt(registry, definitions, def, imports, 'UInt');
}
/** @internal */
function tsVec (registry: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports): string {
const type = (def.sub as TypeDef).type;
if (type === 'u8') {
if (def.info === TypeDefInfo.VecFixed) {
setImports(definitions, imports, ['U8aFixed']);
return exportInterface(def.lookupIndex, def.name, 'U8aFixed');
} else {
setImports(definitions, imports, ['Bytes']);
return exportInterface(def.lookupIndex, def.name, 'Bytes');
}
}
const fmtType = def.lookupName && def.name !== def.lookupName
? def.lookupName
: formatType(registry, definitions, def, imports, false);
return exportInterface(def.lookupIndex, def.name, fmtType);
}
// handlers are defined externally to use - this means that when we do a
// `generators[typedef.info](...)` TS will show any unhandled types. Rather
// we are being explicit in having no handlers where we do not support (yet)
export const typeEncoders: Record<TypeDefInfo, (registry: Registry, definitions: Record<string, ModuleTypes>, def: TypeDef, imports: TypeImports) => string> = {
[TypeDefInfo.BTreeMap]: tsExport,
[TypeDefInfo.BTreeSet]: tsExport,
[TypeDefInfo.Compact]: tsExport,
[TypeDefInfo.DoNotConstruct]: tsExport,
[TypeDefInfo.Enum]: tsEnum,
[TypeDefInfo.HashMap]: tsExport,
[TypeDefInfo.Int]: tsInt,
[TypeDefInfo.Linkage]: errorUnhandled,
[TypeDefInfo.Null]: tsNull,
[TypeDefInfo.Option]: tsExport,
[TypeDefInfo.Plain]: tsExport,
[TypeDefInfo.Range]: tsExport,
[TypeDefInfo.RangeInclusive]: tsExport,
[TypeDefInfo.Result]: tsResult,
[TypeDefInfo.Set]: tsSet,
[TypeDefInfo.Si]: tsSi,
[TypeDefInfo.Struct]: tsStruct,
[TypeDefInfo.Tuple]: tsExport,
[TypeDefInfo.UInt]: tsUInt,
[TypeDefInfo.Vec]: tsVec,
[TypeDefInfo.VecFixed]: tsVec,
[TypeDefInfo.WrapperKeepOpaque]: tsExport,
[TypeDefInfo.WrapperOpaque]: tsExport
};
/** @internal */
function generateInterfaces (registry: Registry, definitions: Record<string, ModuleTypes>, { types }: { types: Record<string, any> }, imports: Imports): [string, string][] {
return Object.entries(types).map(([name, type]): [string, string] => {
const def = getTypeDef(isString(type) ? type : stringify(type), { name });
return [name, typeEncoders[def.info](registry, definitions, def, imports)];
});
}
/** @internal */
export function generateTsDefFor (registry: Registry, importDefinitions: Record<string, Record<string, ModuleTypes>>, defName: string, { types }: { types: Record<string, any> }, outputDir: string): void {
const imports = { ...createImports(importDefinitions, { types }), interfaces: [] } as Imports;
const definitions = imports.definitions;
const interfaces = generateInterfaces(registry, definitions, { types }, imports);
const items = interfaces.sort((a, b) => a[0].localeCompare(b[0])).map(([, definition]) => definition);
writeFile(path.join(outputDir, defName, 'types.ts'), () => generateTsDefModuleTypesTemplate({
headerType: 'defs',
imports,
items,
name: defName,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types/augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
}))
]
}), true);
writeFile(path.join(outputDir, defName, 'index.ts'), () => generateTsDefIndexTemplate({ headerType: 'defs' }), true);
}
/** @internal */
export function generateTsDef (importDefinitions: Record<string, Record<string, ModuleTypes>>, outputDir: string, generatingPackage: string): void {
const registry = new TypeRegistry();
writeFile(path.join(outputDir, 'types.ts'), (): string => {
const definitions = importDefinitions[generatingPackage];
Object.entries(definitions).forEach(([defName, obj]): void => {
console.log(`\tExtracting interfaces for ${defName}`);
generateTsDefFor(registry, importDefinitions, defName, obj, outputDir);
});
return generateTsDefTypesTemplate({
headerType: 'defs',
items: Object.keys(definitions)
});
});
writeFile(path.join(outputDir, 'index.ts'), () => generateTsDefIndexTemplate({ headerType: 'defs' }), true);
}
/** @internal */
export function generateDefaultTsDef (): void {
generateTsDef(
{ '@pezkuwi/types/interfaces': defaultDefinitions },
'packages/types/src/interfaces',
'@pezkuwi/types/interfaces'
);
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { VariantDeprecationInfoV16 } from '@pezkuwi/types/interfaces';
import type { Metadata } from '@pezkuwi/types/metadata/Metadata';
import type { Text } from '@pezkuwi/types/primitive';
import type { Definitions, Registry } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import Handlebars from 'handlebars';
import * as defaultDefs from '@pezkuwi/types/interfaces/definitions';
import lookupDefinitions from '@pezkuwi/types-augment/lookup/definitions';
import { stringCamelCase } from '@pezkuwi/util';
import { compareName, createImports, formatType, getSimilarTypes, initMeta, readTemplate, rebrandTypeName, setImports, writeFile } from '../util/index.js';
import { ignoreUnusedLookups } from './lookup.js';
import { type ExtraTypes, getDeprecationNotice } from './types.js';
const MAPPED_NAMES: Record<string, string> = {
class: 'clazz',
new: 'updated'
};
const generateForMetaTemplate = Handlebars.compile(readTemplate('tx'));
function mapName (_name: Text): string {
const name = stringCamelCase(_name);
return MAPPED_NAMES[name] || name;
}
/** @internal */
function generateForMeta (registry: Registry, meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
writeFile(dest, (): string => {
const allTypes: ExtraTypes = {
'@pezkuwi/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@pezkuwi/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const { lookup, pallets } = meta.asLatest;
const usedTypes = new Set<string>([]);
const modules = pallets
.sort(compareName)
.filter(({ calls }) => calls.isSome)
.map((data) => {
const name = data.name;
const calls = data.calls.unwrap();
const deprecationInfo = calls.deprecationInfo.toJSON();
setImports(allDefs, imports, ['SubmittableExtrinsic']);
const sectionName = stringCamelCase(name);
const items = lookup.getSiType(calls.type).def.asVariant.variants
.map(({ docs, fields, index, name }) => {
const rawStatus = deprecationInfo?.[index.toNumber()];
if (rawStatus) {
const deprecationVariantInfo: VariantDeprecationInfoV16 = meta.registry.createTypeUnsafe('VariantDeprecationInfoV16', [rawStatus]);
const deprecationNotice = getDeprecationNotice(deprecationVariantInfo, name.toString(), 'Call');
const notice = docs.length ? ['', deprecationNotice] : [deprecationNotice];
docs.push(...notice.map((text) => meta.registry.createType('Text', text)));
}
const typesInfo = fields.map(({ name, type, typeName }, index): [string, string, string] => {
const typeDef = registry.lookup.getTypeDef(type);
return [
name.isSome
? mapName(name.unwrap())
: `param${index}`,
rebrandTypeName(typeName.isSome
? typeName.toString()
: typeDef.type),
rebrandTypeName(typeDef.isFromSi
? typeDef.type
: typeDef.lookupName || typeDef.type)
];
});
const params = typesInfo
.map(([name,, typeStr]) => {
const similarTypes = getSimilarTypes(registry, allDefs, typeStr, imports);
setImports(allDefs, imports, [typeStr, ...similarTypes]);
// Add the type to the list of used types
if (!(imports.primitiveTypes[typeStr])) {
usedTypes.add(typeStr);
}
return `${name}: ${similarTypes.join(' | ')}`;
})
.join(', ');
return {
args: typesInfo.map(([,, typeStr]) =>
formatType(registry, allDefs, typeStr, imports)
).join(', '),
docs,
name: stringCamelCase(name),
params
};
})
.sort(compareName);
return {
items,
name: sectionName
};
})
.sort(compareName);
// filter out the unused lookup types from imports
ignoreUnusedLookups([...usedTypes], imports);
return generateForMetaTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@pezkuwi/api-base/types',
types: ['ApiTypes', 'AugmentedSubmittable', 'SubmittableExtrinsic', 'SubmittableExtrinsicFunction']
}
]
});
});
}
// Call `generateForMeta()` with current static metadata
/** @internal */
export function generateDefaultTx (dest: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
const { metadata, registry } = initMeta(data, extraTypes);
return generateForMeta(registry, metadata, dest, extraTypes, isStrict, customLookupDefinitions);
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option, Text } from '@pezkuwi/types-codec';
export type ExtraTypes = Record<string, Record<string, {
runtime?: Record<string, any>;
types: Record<string, any>;
}>>;
export function getDeprecationNotice<T extends { isDeprecated: boolean; asDeprecated: { note: Text; since: Option<Text> }}> (deprecationInfo: T, name: string, label?: string): string {
let deprecationNotice = '@deprecated';
if (deprecationInfo.isDeprecated) {
const { note, since } = deprecationInfo.asDeprecated;
const sinceText = since.isSome ? ` Since ${since.unwrap().toString()}.` : '';
deprecationNotice += ` ${note.toString()}${sinceText}`;
} else {
const labelText = label ? `${label} ` : '';
deprecationNotice += ` ${labelText}${name} has been deprecated`;
}
return deprecationNotice;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './packageDetect.js';
export * from './bundle.js';
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@pezkuwi/util/types';
// TODO: replace v15 to v16 once metadata is updated
import assetHubDicle from '@pezkuwi/types-support/metadata/v15/asset-hub-dicle-hex';
import assetHubPezkuwi from '@pezkuwi/types-support/metadata/v15/asset-hub-pezkuwi-hex';
import bizinikiwi from '@pezkuwi/types-support/metadata/v15/bizinikiwi-hex';
import dicle from '@pezkuwi/types-support/metadata/v15/dicle-hex';
import pezkuwi from '@pezkuwi/types-support/metadata/v15/pezkuwi-hex';
import { generateDefaultConsts, generateDefaultErrors, generateDefaultEvents, generateDefaultInterface, generateDefaultLookup, generateDefaultQuery, generateDefaultRpc, generateDefaultRuntime, generateDefaultTsDef, generateDefaultTx } from './generate/index.js';
const BASE = 'packages/api-augment/src';
const METAS = Object.entries<HexString>({ assetHubDicle, assetHubPezkuwi, dicle, pezkuwi, bizinikiwi });
export function main (): void {
generateDefaultInterface();
generateDefaultLookup();
generateDefaultRpc();
generateDefaultTsDef();
for (const [name, staticMeta] of METAS) {
console.log();
console.log(`*** Generating for ${name}`);
generateDefaultConsts(`${BASE}/${name}/consts.ts`, staticMeta);
generateDefaultErrors(`${BASE}/${name}/errors.ts`, staticMeta);
generateDefaultEvents(`${BASE}/${name}/events.ts`, staticMeta);
generateDefaultQuery(`${BASE}/${name}/query.ts`, staticMeta);
generateDefaultRuntime(`${BASE}/${name}/runtime.ts`, staticMeta);
generateDefaultTx(`${BASE}/${name}/tx.ts`, staticMeta);
}
}
+844
View File
@@ -0,0 +1,844 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataLatest, RuntimeApiMethodMetadataV16, SiLookupTypeId } from '@pezkuwi/types/interfaces';
import type { PortableRegistry } from '@pezkuwi/types/metadata';
import type { Text } from '@pezkuwi/types/primitive';
import type { Codec, DefinitionCall, DefinitionRpcParam, DefinitionsCall, Registry } from '@pezkuwi/types/types';
import type { HexString } from '@pezkuwi/util/types';
import { parse, type Spec } from 'comment-parser';
import fs from 'node:fs';
import path, { dirname, resolve } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { derive } from '@pezkuwi/api-derive';
import { Metadata, TypeRegistry, Vec } from '@pezkuwi/types';
import * as definitions from '@pezkuwi/types/interfaces/definitions';
import { getStorage as getBizinikiwiStorage } from '@pezkuwi/types/metadata/decorate/storage/getStorage';
import { unwrapStorageType } from '@pezkuwi/types/util';
import assetHubDicleMeta from '@pezkuwi/types-support/metadata/v15/asset-hub-dicle-hex';
import assetHubDicleRpc from '@pezkuwi/types-support/metadata/v15/asset-hub-dicle-rpc';
import assetHubDicleVer from '@pezkuwi/types-support/metadata/v15/asset-hub-dicle-ver';
import assetHubPezkuwiMeta from '@pezkuwi/types-support/metadata/v15/asset-hub-pezkuwi-hex';
import assetHubPezkuwiRpc from '@pezkuwi/types-support/metadata/v15/asset-hub-pezkuwi-rpc';
import assetHubPezkuwiVer from '@pezkuwi/types-support/metadata/v15/asset-hub-pezkuwi-ver';
import bizinikiwiMeta from '@pezkuwi/types-support/metadata/v15/bizinikiwi-hex';
import dicleMeta from '@pezkuwi/types-support/metadata/v15/dicle-hex';
import dicleRpc from '@pezkuwi/types-support/metadata/v15/dicle-rpc';
import dicleVer from '@pezkuwi/types-support/metadata/v15/dicle-ver';
import pezkuwiMeta from '@pezkuwi/types-support/metadata/v15/pezkuwi-hex';
import pezkuwiRpc from '@pezkuwi/types-support/metadata/v15/pezkuwi-rpc';
import pezkuwiVer from '@pezkuwi/types-support/metadata/v15/pezkuwi-ver';
import { isHex, stringCamelCase, stringLowerFirst } from '@pezkuwi/util';
import { blake2AsHex } from '@pezkuwi/util-crypto';
import { assertFile, getMetadataViaWs, getRpcMethodsViaWs, getRuntimeVersionViaWs } from './util/index.js';
interface SectionItem {
link?: string;
name: string;
[bullet: string]: undefined | string | Vec<Text>;
}
interface Section {
link?: string;
name: string;
description?: string;
items: SectionItem[];
}
interface Page {
title: string;
description: string;
sections: Section[];
}
type ApiDef = [apiHash: string, apiVersion: number];
interface StaticDef {
meta: HexString;
rpc?: { methods: string[] };
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> = {
'asset-hub-dicle': {
meta: assetHubDicleMeta,
rpc: assetHubDicleRpc,
ver: assetHubDicleVer as unknown as { apis: ApiDef[] }
},
'asset-hub-pezkuwi': {
meta: assetHubPezkuwiMeta,
rpc: assetHubPezkuwiRpc,
ver: assetHubPezkuwiVer as unknown as { apis: ApiDef[] }
},
dicle: {
meta: dicleMeta,
rpc: dicleRpc,
ver: dicleVer as unknown as { apis: ApiDef[] }
},
pezkuwi: {
meta: pezkuwiMeta,
rpc: pezkuwiRpc,
ver: pezkuwiVer as unknown as { apis: ApiDef[] }
},
bizinikiwi: {
meta: bizinikiwiMeta
}
};
/** @internal */
function docsVecToMarkdown (docLines: Vec<Text>, indent = 0): string {
const md = docLines
.map((docLine) =>
docLine
.toString()
.trimStart()
.replace(/^r"/g, '')
.trimStart()
)
.reduce((md, docLine) => // generate paragraphs
!docLine.length
? `${md}\n\n` // empty line
: /^[*-]/.test(docLine.trimStart()) && !md.endsWith('\n\n')
? `${md}\n\n${docLine}` // line calling for a preceding linebreak
: `${md} ${docLine.replace(/^#{1,3} /, '#### ')} `
, '')
.replace(/#### <weight>/g, '<weight>')
.replace(/<weight>(.|\n)*?<\/weight>/g, '')
.replace(/#### Weight:/g, 'Weight:');
// prefix each line with indentation
return md?.split('\n\n').map((line) => `${' '.repeat(indent)}${line}`).join('\n\n');
}
function renderPage (page: Page): string {
let md = `---\ntitle: ${page.title}\n---\n\n`;
if (page.description) {
md += `${page.description}\n\n`;
}
// index
page.sections.forEach((section) => {
md += `- **[${stringCamelCase(section.name)}](#${stringCamelCase(section.name).toLowerCase()})**\n\n`;
});
// contents
page.sections.forEach((section) => {
md += '\n___\n\n\n';
md += section.link
? `<h2 id="#${section.link}">${section.name}</h2>\n`
: `## ${section.name}\n`;
if (section.description) {
md += `\n_${section.description}_\n`;
}
section.items.forEach((item) => {
md += ' \n';
md += item.link
? `<h3 id="#${item.link}">${item.name}</h3>`
: `### ${item.name}`;
Object
.keys(item)
.filter((key) => !['link', 'name'].includes(key))
.forEach((bullet) => {
md += `\n- **${bullet}**: ${
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
item[bullet] instanceof Vec
? docsVecToMarkdown(item[bullet], 2).toString()
: item[bullet]
}`;
});
md += '\n';
});
});
return md;
}
function sortByName<T extends { name: Codec | string }> (a: T, b: T): number {
// case insensitive (all-uppercase) sorting
return a.name.toString().toUpperCase().localeCompare(b.name.toString().toUpperCase());
}
function getSiName (lookup: PortableRegistry, type: SiLookupTypeId): string {
const typeDef = lookup.getTypeDef(type);
return typeDef.lookupName || typeDef.type;
}
/** @internal */
function addRpc (_runtimeDesc: string, rpcMethods?: string[]): string {
return renderPage({
description: 'The following sections contain known RPC methods that may be available on specific nodes (depending on configuration and available pallets) and allow you to interact with the actual node, query, and submit.',
sections: Object
.keys(definitions)
.filter((key) => Object.keys(definitions[key as 'babe'].rpc || {}).length !== 0)
.sort()
.reduce((all: Section[], _sectionName): Section[] => {
const section = definitions[_sectionName as 'babe'];
Object
.keys(section.rpc || {})
.sort()
.forEach((methodName) => {
const method = section.rpc?.[methodName];
if (!method) {
throw new Error(`No ${methodName} RPC found in ${_sectionName}`);
}
const sectionName = method.aliasSection || _sectionName;
const jsonrpc = (method.endpoint || `${sectionName}_${methodName}`);
if (rpcMethods) {
// if we are passing the rpcMethods params and we cannot find this method, skip it
if (jsonrpc !== 'rpc_methods' && !rpcMethods.includes(jsonrpc)) {
return;
}
}
const topName = method.aliasSection ? `${_sectionName}/${method.aliasSection}` : _sectionName;
let container = all.find(({ name }) => name === topName);
if (!container) {
container = { items: [], name: topName };
all.push(container);
}
const args = method.params.map(({ isOptional, name, type }: DefinitionRpcParam): string => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
return name + (isOptional ? '?' : '') + ': `' + type + '`';
}).join(', ');
const type = '`' + method.type + '`';
container.items.push({
interface: '`' + `api.rpc.${sectionName}.${methodName}` + '`',
jsonrpc: '`' + jsonrpc + '`',
// link: jsonrpc,
name: `${methodName}(${args}): ${type}`,
...((method.description && { summary: method.description }) || {}),
...((method.deprecated && { deprecated: method.deprecated }) || {}),
...((method.isUnsafe && { unsafe: 'This method is only active with appropriate flags' }) || {})
});
});
return all;
}, []).sort(sortByName),
title: 'JSON-RPC'
});
}
/** @internal */
function getMethods (registry: Registry, methods: Vec<RuntimeApiMethodMetadataV16>) {
const result: Record<string, DefinitionCall> = {};
methods.forEach((m) => {
const { docs, inputs, name, output } = m;
result[name.toString()] = {
description: docs.map((d) => d.toString()).join(),
params: inputs.map(({ name, type }) => {
return { name: name.toString(), type: registry.lookup.getName(type) || registry.lookup.getTypeDef(type).type };
}),
type: registry.lookup.getName(output) || registry.lookup.getTypeDef(output).type
};
});
return result;
}
/** @internal */
function getRuntimeDefViaMetadata (registry: Registry) {
const result: DefinitionsCall = {};
const { apis } = registry.metadata;
for (let i = 0, count = apis.length; i < count; i++) {
const { methods, name } = apis[i];
result[name.toString()] = [{
methods: getMethods(registry, methods),
// We set the version to 0 here since it will not be relevant when we are grabbing the runtime apis
// from the Metadata.
version: 0
}];
}
return Object.entries(result);
}
function runtimeSections (registry: Registry) {
const sections = getRuntimeDefViaMetadata(registry);
const all = [];
for (let j = 0, jcount = sections.length; j < jcount; j++) {
const [_section, secs] = sections[j];
const sec = secs[0];
const section = stringCamelCase(_section);
const methods = Object.entries(sec.methods);
const container: Section = { items: [], name: section };
all.push(container);
methods
.sort(([a], [b]) => a.localeCompare(b))
.forEach(([methodName, { description, params, type }]): void => {
const args = params.map(({ name, type }): string => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
return name + ': `' + type + '`';
}).join(', ');
container.items.push({
interface: '`' + `api.call.${stringCamelCase(section)}.${stringCamelCase(methodName)}` + '`',
name: `${stringCamelCase(methodName)}(${args}): ${'`' + type + '`'}`,
runtime: '`' + `${section}_${methodName}` + '`',
summary: description
});
});
}
return all.sort(sortByName);
}
/** @internal */
function addRuntime (_runtimeDesc: string, registry: Registry): string {
return renderPage({
description: 'The following section contains known runtime calls that may be available on specific runtimes (depending on configuration and available pallets). These call directly into the WASM runtime for queries and operations.',
sections: runtimeSections(registry),
title: 'Runtime'
});
}
/** @internal */
function addLegacyRuntime (_runtimeDesc: string, _registry: Registry, apis?: ApiDef[]) {
return renderPage({
description: 'The following section contains known runtime calls that may be available on specific runtimes (depending on configuration and available pallets). These call directly into the WASM runtime for queries and operations.',
sections: Object
.keys(definitions)
.filter((key) => Object.keys(definitions[key as 'babe'].runtime || {}).length !== 0)
.sort()
.reduce((all: Section[], _sectionName): Section[] => {
Object
.entries(definitions[_sectionName as 'babe'].runtime || {})
.forEach(([apiName, versions]) => {
versions
.sort((a, b) => b.version - a.version)
.forEach(({ methods, version }, index) => {
if (apis) {
// if we are passing the api hashes and we cannot find this one, skip it
const apiHash = blake2AsHex(apiName, 64);
const api = apis.find(([hash]) => hash === apiHash);
if (!api || api[1] !== version) {
return;
}
} else if (index) {
// we only want the highest version
return;
}
const container: Section = { items: [], name: apiName };
all.push(container);
Object
.entries(methods)
.sort(([a], [b]) => a.localeCompare(b))
.forEach(([methodName, { description, params, type }]): void => {
const args = params.map(({ name, type }): string => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
return name + ': `' + type + '`';
}).join(', ');
container.items.push({
interface: '`' + `api.call.${stringCamelCase(apiName)}.${stringCamelCase(methodName)}` + '`',
name: `${stringCamelCase(methodName)}(${args}): ${'`' + type + '`'}`,
runtime: '`' + `${apiName}_${methodName}` + '`',
summary: description
});
});
});
});
return all;
}, []).sort(sortByName),
title: 'Runtime'
});
}
/** @internal */
function addConstants (runtimeDesc: string, { lookup, pallets }: MetadataLatest): string {
return renderPage({
description: `The following sections contain the module constants, also known as parameter types. These can only be changed as part of a runtime upgrade. On the api, these are exposed via \`api.consts.<module>.<method>\`. ${headerFn(runtimeDesc)}`,
sections: pallets
.sort(sortByName)
.filter(({ constants }) => !constants.isEmpty)
.map(({ constants, name }) => {
const sectionName = stringLowerFirst(name);
return {
items: constants
.sort(sortByName)
.map(({ docs, name, type }) => {
const methodName = stringCamelCase(name);
return {
interface: '`' + `api.consts.${sectionName}.${methodName}` + '`',
name: `${methodName}: ` + '`' + getSiName(lookup, type) + '`',
...(docs.length && { summary: docs })
};
}),
name: sectionName
};
}),
title: 'Constants'
});
}
/** @internal */
function addStorage (runtimeDesc: string, { lookup, pallets, registry }: MetadataLatest): string {
const { bizinikiwi } = getBizinikiwiStorage(registry);
const moduleSections = pallets
.sort(sortByName)
.filter((moduleMetadata) => !moduleMetadata.storage.isNone)
.map((moduleMetadata): Section => {
const sectionName = stringLowerFirst(moduleMetadata.name);
return {
items: moduleMetadata.storage.unwrap().items
.sort(sortByName)
.map((func) => {
let arg = '';
if (func.type.isMap) {
const { hashers, key } = func.type.asMap;
arg = '`' + (
hashers.length === 1
? getSiName(lookup, key)
: lookup.getSiType(key).def.asTuple.map((t) => getSiName(lookup, t)).join(', ')
) + '`';
}
const methodName = stringLowerFirst(func.name);
const outputType = unwrapStorageType(registry, func.type, func.modifier.isOptional);
return {
interface: '`' + `api.query.${sectionName}.${methodName}` + '`',
name: `${methodName}(${arg}): ` + '`' + outputType + '`',
...(func.docs.length && { summary: func.docs })
};
}),
name: sectionName
};
});
return renderPage({
description: `The following sections contain Storage methods are part of the ${runtimeDesc}. On the api, these are exposed via \`api.query.<module>.<method>\`. ${headerFn(runtimeDesc)}`,
sections: moduleSections.concat([{
description: 'These are well-known keys that are always available to the runtime implementation of any Bizinikiwi-based network.',
items: Object.entries(bizinikiwi).map(([name, { meta }]) => {
const arg = meta.type.isMap
? ('`' + getSiName(lookup, meta.type.asMap.key) + '`')
: '';
const methodName = stringLowerFirst(name);
const outputType = unwrapStorageType(registry, meta.type, meta.modifier.isOptional);
return {
interface: '`' + `api.query.bizinikiwi.${methodName}` + '`',
name: `${methodName}(${arg}): ` + '`' + outputType + '`',
summary: meta.docs
};
}),
name: 'bizinikiwi'
}]).sort(sortByName),
title: 'Storage'
});
}
/** @internal */
function addExtrinsics (runtimeDesc: string, { lookup, pallets }: MetadataLatest): string {
return renderPage({
description: `The following sections contain Extrinsics methods are part of the ${runtimeDesc}. On the api, these are exposed via \`api.tx.<module>.<method>\`. ${headerFn(runtimeDesc)}`,
sections: pallets
.sort(sortByName)
.filter(({ calls }) => calls.isSome)
.map(({ calls, name }) => {
const sectionName = stringCamelCase(name);
return {
items: lookup.getSiType(calls.unwrap().type).def.asVariant.variants
.sort(sortByName)
.map(({ docs, fields, name }, index) => {
const methodName = stringCamelCase(name);
const args = fields.map(({ name, type }) =>
`${name.isSome ? name.toString() : `param${index}`}: ` + '`' + getSiName(lookup, type) + '`'
).join(', ');
return {
interface: '`' + `api.tx.${sectionName}.${methodName}` + '`',
name: `${methodName}(${args})`,
...(docs.length && { summary: docs })
};
}),
name: sectionName
};
}),
title: 'Extrinsics'
});
}
/** @internal */
function addEvents (runtimeDesc: string, { lookup, pallets }: MetadataLatest): string {
return renderPage({
description: `Events are emitted for certain operations on the runtime. The following sections describe the events that are part of the ${runtimeDesc}. ${headerFn(runtimeDesc)}`,
sections: pallets
.sort(sortByName)
.filter(({ events }) => events.isSome)
.map((meta) => ({
items: lookup.getSiType(meta.events.unwrap().type).def.asVariant.variants
.sort(sortByName)
.map(({ docs, fields, name }) => {
const methodName = name.toString();
const args = fields.map(({ type }) =>
'`' + getSiName(lookup, type) + '`'
).join(', ');
return {
interface: '`' + `api.events.${stringCamelCase(meta.name)}.${methodName}.is` + '`',
name: `${methodName}(${args})`,
...(docs.length && { summary: docs })
};
}),
name: stringCamelCase(meta.name)
})),
title: 'Events'
});
}
/** @internal */
function addErrors (runtimeDesc: string, { lookup, pallets }: MetadataLatest): string {
return renderPage({
description: `This page lists the errors that can be encountered in the different modules. ${headerFn(runtimeDesc)}`,
sections: pallets
.sort(sortByName)
.filter(({ errors }) => errors.isSome)
.map((moduleMetadata) => ({
items: lookup.getSiType(moduleMetadata.errors.unwrap().type).def.asVariant.variants
.sort(sortByName)
.map((error) => ({
interface: '`' + `api.errors.${stringCamelCase(moduleMetadata.name)}.${error.name.toString()}.is` + '`',
name: error.name.toString(),
...(error.docs.length && { summary: error.docs })
})),
name: stringLowerFirst(moduleMetadata.name)
})),
title: 'Errors'
});
}
function getDependencyBasePath (moduleName: string): string {
const modulePath = import.meta.resolve(moduleName);
return resolve(dirname(fileURLToPath(modulePath)));
}
const BASE_DERIVE_PATH = getDependencyBasePath('@pezkuwi/api-derive');
// 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('.js'))
.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}\n`)
);
}
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' });
writeStream.on('finish', (): void => {
console.log(`Completed writing ${name}`);
});
chunks.forEach((chunk): void => {
writeStream.write(chunk);
});
writeStream.end();
}
interface ArgV { chain?: string; endpoint?: string; metadataVer?: number; }
async function mainPromise (): Promise<void> {
const { chain, endpoint, metadataVer } = yargs(hideBin(process.argv)).strict().options({
chain: {
description: 'The chain name to use for the output (defaults to "Bizinikiwi")',
type: 'string'
},
endpoint: {
description: 'The endpoint to connect to (e.g. wss://dicle-rpc.pezkuwi.io) or relative path to a file containing the JSON output of an RPC state_getMetadata call',
type: 'string'
},
metadataVer: {
description: 'The metadata version to use for generating type information. This will use state_call::Metadata_metadata_at_version to query metadata',
type: 'number'
}
}).argv as ArgV;
/**
* This is unique to when the endpoint arg is used. Since the endpoint requires us to query the chain, it may query the chains
* rpc state_getMetadata method to get metadata, but this restricts us to v14 only. Therefore we must also check if the `metadataVer` is passed
* in as well. These checks will help us decide if we are using v14 or newer.
*/
const useV14Metadata = endpoint && ((metadataVer && metadataVer < 15) || !metadataVer);
const chainName = chain || 'Bizinikiwi';
let metaHex: HexString;
let rpcMethods: string[] | undefined;
let runtimeApis: ApiDef[] | undefined;
if (endpoint) {
if (endpoint.startsWith('wss://') || endpoint.startsWith('ws://')) {
metaHex = await getMetadataViaWs(endpoint, metadataVer);
rpcMethods = await getRpcMethodsViaWs(endpoint);
runtimeApis = await getRuntimeVersionViaWs(endpoint);
} else {
metaHex = (
JSON.parse(
fs.readFileSync(assertFile(path.join(process.cwd(), endpoint)), 'utf-8')
) as { result: HexString }
).result;
if (!isHex(metaHex)) {
throw new Error('Invalid metadata file');
}
}
} else if (ALL_STATIC[chainName.toLowerCase()]) {
metaHex = ALL_STATIC[chainName.toLowerCase()].meta;
rpcMethods = ALL_STATIC[chainName.toLowerCase()].rpc?.methods;
} else {
metaHex = bizinikiwiMeta;
}
let metadata: Metadata;
const registry = new TypeRegistry();
if (useV14Metadata) {
metadata = new Metadata(registry, metaHex);
} else {
const opaqueMetadata = registry.createType('Option<OpaqueMetadata>', registry.createType('Raw', metaHex).toU8a()).unwrap();
metadata = new Metadata(registry, opaqueMetadata.toHex());
}
registry.setMetadata(metadata);
const latest = metadata.asLatest;
const runtimeDesc = `default ${chainName} runtime`;
const docRoot = `docs/${chainName.toLowerCase()}`;
writeFile(`${docRoot}/rpc.md`, addRpc(runtimeDesc, rpcMethods));
useV14Metadata
? writeFile(`${docRoot}/runtime.md`, addLegacyRuntime(runtimeDesc, registry, runtimeApis))
: writeFile(`${docRoot}/runtime.md`, addRuntime(runtimeDesc, registry));
writeFile(`${docRoot}/constants.md`, addConstants(runtimeDesc, latest));
writeFile(`${docRoot}/storage.md`, addStorage(runtimeDesc, latest));
writeFile(`${docRoot}/extrinsics.md`, addExtrinsics(runtimeDesc, latest));
writeFile(`${docRoot}/events.md`, addEvents(runtimeDesc, latest));
writeFile(`${docRoot}/errors.md`, addErrors(runtimeDesc, latest));
if (chainName === 'Bizinikiwi') {
writeFile('docs/derives/derives.md', generateDerives());
}
}
export function main (): void {
mainPromise().catch((error) => {
console.error();
console.error(error);
console.error();
process.exit(1);
});
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2026 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @pezkuwi/dev
// (packageInfo imports will be kept as-is, user-editable)
import { packageInfo as apiInfo } from '@pezkuwi/api/packageInfo';
import { packageInfo as providerInfo } from '@pezkuwi/rpc-provider/packageInfo';
import { packageInfo as typesInfo } from '@pezkuwi/types/packageInfo';
import { detectPackage } from '@pezkuwi/util';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, [apiInfo, providerInfo, typesInfo]);
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2026 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @pezkuwi/dev
export const packageInfo = { name: '@pezkuwi/typegen', path: 'auto', type: 'auto', version: '16.5.4' };
+30
View File
@@ -0,0 +1,30 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/calls';
{{{ importsAll }}}
export type __AugmentedCall<ApiType extends ApiTypes> = AugmentedCall<ApiType>;
export type __DecoratedCallBase<ApiType extends ApiTypes> = DecoratedCallBase<ApiType>;
declare module '@pezkuwi/api-base/types/calls' {
interface AugmentedCalls<ApiType extends ApiTypes> {
{{#each modules}}
/** {{{sectionHash}}}/{{{sectionVersion}}} */
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedCall<ApiType, ({{{args}}}) => Observable<{{{type}}}>>;
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic call
**/
[key: string]: DecoratedCallBase<ApiType>;
{{/unless}}
};
{{/each}}
} // AugmentedCalls
} // declare module
+28
View File
@@ -0,0 +1,28 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/consts';
{{{ importsAll }}}
export type __AugmentedConst<ApiType extends ApiTypes> = AugmentedConst<ApiType>;
declare module '@pezkuwi/api-base/types/consts' {
interface AugmentedConsts<ApiType extends ApiTypes> {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: {{{type}}} & AugmentedConst<ApiType>;
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic const
**/
[key: string]: Codec;
{{/unless}}
};
{{/each}}
} // AugmentedConsts
} // declare module
+7
View File
@@ -0,0 +1,7 @@
{{#if docs.length}}
/**
{{#each docs}}
* {{#trim}}{{{this}}}{{/trim}}
{{/each}}
**/
{{/if}}
+28
View File
@@ -0,0 +1,28 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/errors';
{{{ importsAll }}}
export type __AugmentedError<ApiType extends ApiTypes> = AugmentedError<ApiType>;
declare module '@pezkuwi/api-base/types/errors' {
interface AugmentedErrors<ApiType extends ApiTypes> {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedError<ApiType>;
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic error
**/
[key: string]: AugmentedError<ApiType>;
{{/unless}}
};
{{/each}}
} // AugmentedErrors
} // declare module
+28
View File
@@ -0,0 +1,28 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/events';
{{{ importsAll }}}
export type __AugmentedEvent<ApiType extends ApiTypes> = AugmentedEvent<ApiType>;
declare module '@pezkuwi/api-base/types/events' {
interface AugmentedEvents<ApiType extends ApiTypes> {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedEvent<ApiType, {{{type}}}>;
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic event
**/
[key: string]: AugmentedEvent<ApiType>;
{{/unless}}
};
{{/each}}
} // AugmentedEvents
} // declare module
@@ -0,0 +1,2 @@
// Auto-generated via `yarn polkadot-types-from-{{headerType}}`, do not edit
/* eslint-disable */
@@ -0,0 +1,15 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/types/types/registry';
{{{ importsAll }}}
declare module '@pezkuwi/types/types/registry' {
interface InterfaceTypes {
{{#each items}}
{{{this}}}: {{{this}}};
{{/each}}
} // InterfaceTypes
} // declare module
@@ -0,0 +1,12 @@
{{> header }}
/* eslint-disable sort-keys */
export default {
{{#each defs}}
{{> docs}}
{{#each defs}}
{{{this}}}
{{/each}}
{{/each}}
};
@@ -0,0 +1,15 @@
{{> header }}
/* eslint-disable sort-keys */
export default {
rpc: {},
types: {
{{#each defs}}
{{> docs}}
{{#each defs}}
{{{this}}}
{{/each}}
{{/each}}
}
};
@@ -0,0 +1,3 @@
{{> header }}
export * from './types.js';
@@ -0,0 +1,14 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/types/lookup';
{{{ importsAll }}}
declare module '@pezkuwi/types/lookup' {
{{#each items}}
{{{this}}}
{{/each}}
} // declare module
+29
View File
@@ -0,0 +1,29 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/storage';
{{{ importsAll }}}
export type __AugmentedQuery<ApiType extends ApiTypes> = AugmentedQuery<ApiType, () => unknown>;
export type __QueryableStorageEntry<ApiType extends ApiTypes> = QueryableStorageEntry<ApiType>;
declare module '@pezkuwi/api-base/types/storage' {
interface AugmentedQueries<ApiType extends ApiTypes> {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: {{{entryType}}}<ApiType, ({{{params}}}) => Observable<{{{returnType}}}>, [{{{args}}}]>{{#unless @root.isStrict}} & QueryableStorageEntry<ApiType, [{{{args}}}]>{{/unless}};
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic query
**/
[key: string]: QueryableStorageEntry<ApiType>;
{{/unless}}
};
{{/each}}
} // AugmentedQueries
} // declare module
+22
View File
@@ -0,0 +1,22 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/rpc-core/types/jsonrpc';
{{{ importsAll }}}
export type __AugmentedRpc = AugmentedRpc<() => unknown>;
declare module '@pezkuwi/rpc-core/types/jsonrpc' {
interface RpcInterface {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedRpc<{{#if generic}}<{{{generic}}}>{{/if}}({{{args}}}) => Observable<{{{type}}}>>;
{{/each}}
};
{{/each}}
} // RpcInterface
} // declare module
@@ -0,0 +1,3 @@
{{> header }}
export * from './types.js';
@@ -0,0 +1,10 @@
{{> header }}
{{{ importsAll }}}
{{#each items}}
{{{this}}}
{{/each}}
export type PHANTOM_{{#upper}}{{{name}}}{{/upper}} = '{{{name}}}';
@@ -0,0 +1,7 @@
{{> header }}
{{#each items}}
export * from './{{{this}}}/types.js';
{{/each}}
export type PHANTOM_GENERATED = 'generated';
+30
View File
@@ -0,0 +1,30 @@
{{> header }}
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/api-base/types/submittable';
{{{ importsAll }}}
export type __AugmentedSubmittable = AugmentedSubmittable<() => unknown>;
export type __SubmittableExtrinsic<ApiType extends ApiTypes> = SubmittableExtrinsic<ApiType>;
export type __SubmittableExtrinsicFunction<ApiType extends ApiTypes> = SubmittableExtrinsicFunction<ApiType>;
declare module '@pezkuwi/api-base/types/submittable' {
interface AugmentedSubmittables<ApiType extends ApiTypes> {
{{#each modules}}
{{{name}}}: {
{{#each items}}
{{> docs}}
{{{name}}}: AugmentedSubmittable<({{{params}}}) => SubmittableExtrinsic<ApiType>, [{{{args}}}]>;
{{/each}}
{{#unless @root.isStrict}}
/**
* Generic tx
**/
[key: string]: SubmittableExtrinsicFunction<ApiType>;
{{/unless}}
};
{{/each}}
} // AugmentedSubmittables
} // declare module
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import fs from 'node:fs';
import { assert } from '@pezkuwi/util';
export function assertDir (path: string): string {
assert(fs.existsSync(path) && fs.lstatSync(path).isDirectory(), `${path} is not a directory`);
return path;
}
export function assertFile (path: string): string {
assert(fs.existsSync(path) && fs.lstatSync(path).isFile(), `${path} is not a file`);
return path;
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types/create';
import { getSimilarTypes } from './derived.js';
describe('getSimilarTypes', (): void => {
const registry = new TypeRegistry();
const mockImports = {
codecTypes: {},
definitions: {},
extrinsicTypes: {},
genericTypes: {},
ignoredTypes: [],
localTypes: {},
lookupTypes: {},
metadataTypes: {},
primitiveTypes: {},
typeToModule: {},
typesTypes: {}
};
it('handles nested Tuples', (): void => {
expect(getSimilarTypes(registry, {}, '(AccountId, (Balance, u32), u64)', mockImports)).toEqual([
'ITuple<[AccountId, ITuple<[Balance, u32]>, u64]>',
'[AccountId | string | Uint8Array, ITuple<[Balance, u32]> | [Balance | AnyNumber | Uint8Array, u32 | AnyNumber | Uint8Array], u64 | AnyNumber | Uint8Array]'
]);
});
it('handles vectors of slices', (): void => {
expect(getSimilarTypes(registry, {}, 'Vec<[u8;4]>', mockImports)).toEqual([
'Vec<U8aFixed>'
]);
expect(getSimilarTypes(registry, {}, 'Vec<[Balance;8]>', mockImports)).toEqual([
'Vec<Vec<Balance>>'
]);
});
it('handles structs', (): void => {
expect(getSimilarTypes(registry, {}, '{ "a": "u8", "b": "Vec<u8>" }', mockImports)).toEqual([
`{
readonly a: u8;
readonly b: Bytes;
} & Struct`, '{ a?: any; b?: any }', 'string', 'Uint8Array'
]);
});
it('handles vectors of structs', (): void => {
expect(getSimilarTypes(registry, {}, 'Vec<{ "a": "H256", "b": "Vec<H256>" }>', mockImports)).toEqual([
`Vec<{
readonly a: H256;
readonly b: Vec<H256>;
} & Struct>`
]);
});
});
+133
View File
@@ -0,0 +1,133 @@
// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { CodecClass, Registry } from '@pezkuwi/types/types';
import type { UInt } from '@pezkuwi/types-codec';
import type { TypeDef } from '@pezkuwi/types-create/types';
import type { ModuleTypes, TypeImports } from './imports.js';
import { GenericAccountId, GenericCall, GenericLookupSource, GenericVote } from '@pezkuwi/types/generic';
import { AllConvictions } from '@pezkuwi/types/interfaces/democracy/definitions';
import { AbstractInt, bool, Compact, Enum, Null, Option, Struct, Tuple, Vec, WrapperKeepOpaque, WrapperOpaque } from '@pezkuwi/types-codec';
import { getTypeDef, TypeDefInfo } from '@pezkuwi/types-create';
import { isChildClass, stringify } from '@pezkuwi/util';
import { formatType } from './formatting.js';
import { setImports } from './imports.js';
function arrayToStrType (arr: string[]): string {
return `${arr.map((c) => `'${c}'`).join(' | ')}`;
}
const voteConvictions = arrayToStrType(AllConvictions);
// Make types a little bit more flexible
// - if param instanceof AbstractInt, then param: u64 | Uint8array | AnyNumber
// etc
/** @internal */
export function getSimilarTypes (registry: Registry, definitions: Record<string, ModuleTypes>, _type: string, imports: TypeImports): string[] {
const typeParts = _type.split('::');
const type = typeParts[typeParts.length - 1];
const possibleTypes = [formatType(registry, definitions, type, imports)];
if (type === 'Extrinsic') {
setImports(definitions, imports, ['IExtrinsic']);
return ['Extrinsic', 'IExtrinsic', 'string', 'Uint8Array'];
} else if (type === 'Keys') {
// This one is weird... surely it should popup as a Tuple? (but either way better as defined hex)
return ['Keys', 'string', 'Uint8Array'];
} else if (type === 'StorageKey') {
// TODO We can do better
return ['StorageKey', 'string', 'Uint8Array', 'any'];
} else if (type === '()') {
return ['null'];
}
const Clazz = registry.createClass(type);
if (isChildClass(Vec, Clazz)) {
const vecDef = getTypeDef(type);
const subDef = (vecDef.sub) as TypeDef;
// this could be that we define a Vec type and refer to it by name
if (subDef) {
if (subDef.info === TypeDefInfo.Plain) {
possibleTypes.push(`(${getSimilarTypes(registry, definitions, subDef.type, imports).join(' | ')})[]`);
} else if (subDef.info === TypeDefInfo.Tuple) {
const subs = (subDef.sub as TypeDef[]).map(({ type }): string =>
getSimilarTypes(registry, definitions, type, imports).join(' | ')
);
possibleTypes.push(`([${subs.join(', ')}])[]`);
} else if (subDef.info === TypeDefInfo.Option || subDef.info === TypeDefInfo.Vec || subDef.info === TypeDefInfo.VecFixed) {
// TODO: Add possibleTypes so imports work
} else if (subDef.info === TypeDefInfo.Struct) {
// TODO: Add possibleTypes so imports work
} else {
throw new Error(`Unhandled subtype in Vec, ${stringify(subDef)}`);
}
}
} else if (isChildClass(Enum, Clazz)) {
const { defKeys, isBasic } = new (Clazz as CodecClass)(registry) as Enum;
const keys = defKeys.filter((v) => !v.startsWith('__Unused'));
if (isBasic) {
possibleTypes.push(arrayToStrType(keys), 'number');
} else {
// TODO We don't really want any here, these should be expanded
possibleTypes.push(...keys.map((k) => `{ ${k}: any }`), 'string');
}
possibleTypes.push('Uint8Array');
} else if (isChildClass(AbstractInt as unknown as CodecClass<UInt>, Clazz) || isChildClass(Compact, Clazz)) {
possibleTypes.push('AnyNumber', 'Uint8Array');
} else if (isChildClass(GenericLookupSource, Clazz)) {
possibleTypes.push('Address', 'AccountId', 'AccountIndex', 'LookupSource', 'string', 'Uint8Array');
} else if (isChildClass(GenericAccountId, Clazz)) {
possibleTypes.push('string', 'Uint8Array');
} else if (isChildClass(GenericCall, Clazz)) {
possibleTypes.push('IMethod', 'string', 'Uint8Array');
} else if (isChildClass(bool, Clazz)) {
possibleTypes.push('boolean', 'Uint8Array');
} else if (isChildClass(Null, Clazz)) {
possibleTypes.push('null');
} else if (isChildClass(Struct, Clazz)) {
const s = new (Clazz as CodecClass)(registry) as Struct;
const obj = s.defKeys.map((key): string => `${key}?: any`).join('; ');
possibleTypes.push(`{ ${obj} }`, 'string', 'Uint8Array');
} else if (isChildClass(Option, Clazz)) {
possibleTypes.push('null', 'Uint8Array');
const optDef = getTypeDef(type);
const subDef = (optDef.sub) as TypeDef;
if (subDef) {
possibleTypes.push(...getSimilarTypes(registry, definitions, subDef.type, imports));
} else {
possibleTypes.push('object', 'string');
}
} else if (isChildClass(GenericVote, Clazz)) {
possibleTypes.push(`{ aye: boolean; conviction?: ${voteConvictions} | number }`, 'boolean', 'string', 'Uint8Array');
} else if (isChildClass(WrapperKeepOpaque, Clazz) || isChildClass(WrapperOpaque, Clazz)) {
// TODO inspect container
possibleTypes.push('object', 'string', 'Uint8Array');
} else if (isChildClass(Uint8Array, Clazz)) {
possibleTypes.push('string', 'Uint8Array');
} else if (isChildClass(String, Clazz)) {
possibleTypes.push('string');
} else if (isChildClass(Tuple, Clazz)) {
const tupDef = getTypeDef(type);
const subDef = tupDef.sub;
// this could be that we define a Tuple type and refer to it by name
if (Array.isArray(subDef)) {
const subs = subDef.map(({ type }) => getSimilarTypes(registry, definitions, type, imports).join(' | '));
possibleTypes.push(`[${subs.join(', ')}]`);
}
}
return [...new Set(possibleTypes)];
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import Handlebars from 'handlebars';
import { readTemplate } from './file.js';
Handlebars.registerPartial({
docs: Handlebars.compile(readTemplate('docs'))
});
// empty export
export const __TYPEGEN_DUMMY_DOCS = 'DUMMY_DOCS';
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { packageInfo } from '../packageInfo.js';
export function writeFile (dest: string, generator: () => string, noLog?: boolean): void {
!noLog && console.log(`${dest}\n\tGenerating`);
let generated = generator();
while (generated.includes('\n\n\n')) {
generated = generated.replace(/\n\n\n/g, '\n\n');
}
!noLog && console.log('\tWriting');
fs.writeFileSync(dest, generated, { flag: 'w' });
!noLog && console.log('');
}
export function readTemplate (template: string): string {
// Inside the api repo itself, it will be 'auto'
const rootDir = packageInfo.path === 'auto'
? path.join(process.cwd(), 'packages/typegen/src')
: packageInfo.path;
// NOTE With cjs in a subdir, search one lower as well
const file = ['./templates', '../templates']
.map((p) => path.join(rootDir, p, `${template}.hbs`))
.find((p) => fs.existsSync(p));
if (!file) {
throw new Error(`Unable to locate ${template}.hbs from ${rootDir}`);
}
return fs.readFileSync(file).toString();
}
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types';
import { formatType } from './formatting.js';
describe('formatType', (): void => {
const registry = new TypeRegistry();
it('handles nested Tuples', (): void => {
expect(
formatType(registry, {}, '(AccountId, (Balance, u32), u64)', {
codecTypes: {},
definitions: {},
extrinsicTypes: {},
genericTypes: {},
ignoredTypes: [],
localTypes: {},
lookupTypes: {},
metadataTypes: {},
primitiveTypes: {},
typeToModule: {},
typesTypes: {}
})
).toEqual('ITuple<[AccountId, ITuple<[Balance, u32]>, u64]>');
});
});
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AnyString, Registry } from '@pezkuwi/types/types';
import type { LookupString } from '@pezkuwi/types-codec/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import type { ModuleTypes, TypeImports } from './imports.js';
import Handlebars from 'handlebars';
import * as typesCodec from '@pezkuwi/types-codec';
import { getTypeDef, paramsNotation, TypeDefInfo } from '@pezkuwi/types-create';
import { isString, stringify } from '@pezkuwi/util';
import { readTemplate } from './file.js';
import { setImports } from './imports.js';
// Rebrand type names from Polkadot SDK to Pezkuwi SDK
function rebrandTypeName (typeName: string): string {
return typeName
// Main projects
.replace(/Polkadot/g, 'Pezkuwi')
.replace(/Substrate/g, 'Bizinikiwi')
.replace(/Cumulus/g, 'Pezcumulus')
// Networks
.replace(/Kusama/g, 'Dicle')
.replace(/Westend/g, 'Zagros')
.replace(/Rococo/g, 'Pezkuwichain')
// Chain types
.replace(/Parachain/g, 'Teyrchain')
// Module prefixes (in PascalCase type names)
.replace(/Frame(?=[A-Z])/g, 'Pezframe')
.replace(/Pallet(?=[A-Z])/g, 'Pezpallet')
// Primitives prefixes (sp_ becomes Sp in type names)
.replace(/^Sp(?=[A-Z])/g, 'Pezsp')
.replace(/([^a-zA-Z])Sp(?=[A-Z])/g, '$1Pezsp')
// Client prefixes (sc_ becomes Sc in type names)
.replace(/^Sc(?=[A-Z])/g, 'Pezsc')
.replace(/([^a-zA-Z])Sc(?=[A-Z])/g, '$1Pezsc');
}
interface ImportDef {
file: string;
types: string[];
}
interface This {
imports: TypeImports;
types: ImportDef[];
}
const NO_CODEC = ['Tuple', 'VecFixed'];
const ON_CODEC = Object.keys(typesCodec);
const ON_CODEC_TYPES = ['Codec', 'AnyJson', 'AnyFunction', 'AnyNumber', 'AnyString', 'AnyTuple', 'AnyU8a', 'ICompact', 'IEnum', 'IMap', 'INumber', 'IOption', 'IResult', 'ISet', 'IStruct', 'ITuple', 'IU8a', 'IVec', 'IMethod'];
export const HEADER = (type: 'chain' | 'defs'): string => `// Auto-generated via \`yarn pezkuwi-types-from-${type}\`, do not edit\n/* eslint-disable */\n\n`;
function extractImports ({ imports, types }: This): string[] {
const toplevel = [
...Object.keys(imports.codecTypes),
...Object.keys(imports.extrinsicTypes),
...Object.keys(imports.genericTypes),
...Object.keys(imports.metadataTypes),
...Object.keys(imports.primitiveTypes)
];
return [
{
file: '@pezkuwi/types',
types: toplevel.filter((n) =>
!NO_CODEC.includes(n) && !ON_CODEC.includes(n)
)
},
{
file: '@pezkuwi/types/lookup',
types: Object.keys(imports.lookupTypes)
},
{
file: '@pezkuwi/types/types',
types: Object.keys(imports.typesTypes).filter((n) =>
!ON_CODEC_TYPES.includes(n)
)
},
{
file: '@pezkuwi/types-codec',
types: toplevel.filter((n) =>
!NO_CODEC.includes(n) && ON_CODEC.includes(n)
)
},
{
file: '@pezkuwi/types-codec/types',
types: Object.keys(imports.typesTypes).filter((n) =>
ON_CODEC_TYPES.includes(n)
)
},
...types
]
.filter(({ types }) => types.length)
.sort(({ file }, b) => file.localeCompare(b.file))
.map(({ file, types }) => `import type { ${types.sort().join(', ')} } from '${file}';`);
}
Handlebars.registerPartial({
header: Handlebars.compile(readTemplate('header'))
});
Handlebars.registerHelper({
importsAll () {
return extractImports(this as unknown as This)
.join('\n');
},
importsPackage () {
return extractImports(this as unknown as This)
.filter((l) => !l.includes("from '."))
.join('\n ');
},
importsRelative () {
return extractImports(this as unknown as This)
.filter((l) => l.includes("from '."))
.join('\n');
},
trim (options: { fn: (self: unknown) => string }) {
return options.fn(this).trim();
},
upper (options: { fn: (self: unknown) => string }) {
return options.fn(this).toUpperCase();
}
});
// helper to generate a `export interface <Name> extends <Base> {<Body>}
/** @internal */
export function exportInterface (lookupIndex = -1, name = '', base: string, body = '', withShortcut = false): string {
// * @description extends [[${base}]]
const doc = withShortcut
? ''
: `/** @name ${name}${lookupIndex !== -1 ? ` (${lookupIndex})` : ''} */\n`;
return `${doc}${withShortcut ? '' : `export interface ${name} extends ${base} `}{${body.length ? '\n' : ''}${body}${withShortcut ? ' ' : ''}}`;
}
function singleParamNotation (registry: Registry, wrapper: string, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean): string {
const sub = (typeDef.sub as TypeDef);
const lookupName = rebrandTypeName(sub.lookupName || '');
setImports(definitions, imports, [wrapper, lookupName]);
return paramsNotation(wrapper, lookupName || formatType(registry, definitions, sub.type, imports, withShortcut));
}
function dualParamsNotation (registry: Registry, wrapper: string, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean): string {
const [a, b] = (typeDef.sub as TypeDef[]);
const aLookupName = rebrandTypeName(a.lookupName || '');
const bLookupName = rebrandTypeName(b.lookupName || '');
setImports(definitions, imports, [wrapper, aLookupName, bLookupName]);
return paramsNotation(wrapper, [
aLookupName || formatType(registry, definitions, a.type, imports, withShortcut),
bLookupName || formatType(registry, definitions, b.type, imports, withShortcut)
]);
}
const formatters: Record<TypeDefInfo, (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => string> = {
[TypeDefInfo.Compact]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'Compact', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.DoNotConstruct]: (_registry: Registry, _typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, _withShortcut: boolean) => {
setImports(definitions, imports, ['DoNotConstruct']);
return 'DoNotConstruct';
},
[TypeDefInfo.Enum]: (_registry: Registry, typeDef: TypeDef, _definitions: Record<string, ModuleTypes>, _imports: TypeImports, _withShortcut: boolean) => {
if (typeDef.lookupName) {
return rebrandTypeName(typeDef.lookupName);
}
throw new Error(`TypeDefInfo.Enum: Parameter formatting not implemented on ${stringify(typeDef)}`);
},
[TypeDefInfo.Int]: (_registry: Registry, typeDef: TypeDef, _definitions: Record<string, ModuleTypes>, _imports: TypeImports, _withShortcut: boolean) => {
throw new Error(`TypeDefInfo.Int: Parameter formatting not implemented on ${stringify(typeDef)}`);
},
[TypeDefInfo.UInt]: (_registry: Registry, typeDef: TypeDef, _definitions: Record<string, ModuleTypes>, _imports: TypeImports, _withShortcut: boolean) => {
throw new Error(`TypeDefInfo.UInt: Parameter formatting not implemented on ${stringify(typeDef)}`);
},
[TypeDefInfo.Null]: (_registry: Registry, _typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, _withShortcut: boolean) => {
setImports(definitions, imports, ['Null']);
return 'Null';
},
[TypeDefInfo.Option]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'Option', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.Plain]: (_registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, _withShortcut: boolean) => {
setImports(definitions, imports, [rebrandTypeName(typeDef.type)]);
return rebrandTypeName(typeDef.type);
},
[TypeDefInfo.Range]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'Range', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.RangeInclusive]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'RangeInclusive', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.Set]: (_registry: Registry, typeDef: TypeDef, _definitions: Record<string, ModuleTypes>, _imports: TypeImports, _withShortcut: boolean) => {
throw new Error(`TypeDefInfo.Set: Parameter formatting not implemented on ${stringify(typeDef)}`);
},
[TypeDefInfo.Si]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return formatType(registry, definitions, registry.lookup.getTypeDef(typeDef.type as LookupString), imports, withShortcut);
},
[TypeDefInfo.Struct]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
if (typeDef.lookupName) {
return rebrandTypeName(typeDef.lookupName);
}
const sub = typeDef.sub as TypeDef[];
setImports(definitions, imports, ['Struct', ...sub.map(({ lookupName }) => rebrandTypeName(lookupName || ''))]);
return `{${withShortcut ? ' ' : '\n'}${
sub.map(({ lookupName, name, type }, index) => [
name || `unknown${index}`,
rebrandTypeName(lookupName || '') || formatType(registry, definitions, type, imports, withShortcut)
]).map(([k, t]) => `${withShortcut ? '' : ' readonly '}${k}: ${t};`).join(withShortcut ? ' ' : '\n')
}${withShortcut ? ' ' : '\n '}} & Struct`;
},
[TypeDefInfo.Tuple]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
const sub = typeDef.sub as TypeDef[];
setImports(definitions, imports, ['ITuple', ...sub.map(({ lookupName }) => rebrandTypeName(lookupName || ''))]);
// `(a,b)` gets transformed into `ITuple<[a, b]>`
return paramsNotation('ITuple', `[${
sub.map(({ lookupName, type }) =>
rebrandTypeName(lookupName || '') || formatType(registry, definitions, type, imports, withShortcut)
).join(', ')
}]`);
},
[TypeDefInfo.Vec]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'Vec', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.VecFixed]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
const sub = (typeDef.sub as TypeDef);
if (sub.type === 'u8') {
setImports(definitions, imports, ['U8aFixed']);
return 'U8aFixed';
}
return singleParamNotation(registry, 'Vec', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.BTreeMap]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return dualParamsNotation(registry, 'BTreeMap', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.BTreeSet]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'BTreeSet', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.HashMap]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return dualParamsNotation(registry, 'HashMap', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.Linkage]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'Linkage', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.Result]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return dualParamsNotation(registry, 'Result', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.WrapperKeepOpaque]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'WrapperKeepOpaque', typeDef, definitions, imports, withShortcut);
},
[TypeDefInfo.WrapperOpaque]: (registry: Registry, typeDef: TypeDef, definitions: Record<string, ModuleTypes>, imports: TypeImports, withShortcut: boolean) => {
return singleParamNotation(registry, 'WrapperOpaque', typeDef, definitions, imports, withShortcut);
}
};
/**
* Correctly format a given type
*/
/** @internal */
// eslint-disable-next-line @typescript-eslint/ban-types
export function formatType (registry: Registry, definitions: Record<string, ModuleTypes>, type: AnyString | TypeDef, imports: TypeImports, withShortcut = false): string {
let typeDef: TypeDef;
if (isString(type)) {
const _type = type.toString();
// If type is "unorthodox" (i.e. `{ something: any }` for an Enum input or `[a | b | c, d | e | f]` for a Tuple's similar types),
// we return it as-is
if (withShortcut && /(^{.+:.+})|^\([^,]+\)|^\(.+\)\[\]|^\[.+\]/.exec(_type) && !/\[\w+;\w+\]/.exec(_type)) {
return rebrandTypeName(_type);
}
typeDef = getTypeDef(type);
} else {
typeDef = type;
}
setImports(definitions, imports, [rebrandTypeName(typeDef.lookupName || typeDef.type)]);
return rebrandTypeName(formatters[typeDef.info](registry, typeDef, definitions, imports, withShortcut));
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import * as codecClasses from '@pezkuwi/types/codec';
import * as extrinsicClasses from '@pezkuwi/types/extrinsic';
import * as genericClasses from '@pezkuwi/types/generic';
import * as primitiveClasses from '@pezkuwi/types/primitive';
import { getTypeDef, TypeDefInfo } from '@pezkuwi/types-create';
export interface ModuleTypes {
types: Record<string, unknown>;
}
// these map all the codec and primitive types for import, see the TypeImports below. If
// we have an unseen type, it is `undefined`/`false`, if we need to import it, it is `true`
type TypeExist = Record<string, boolean>;
// local and absolute imports map to this format
// { [moduleName]: { [typeName]: true } }
type TypeExistMap = Record<string, TypeExist>;
export interface TypeImports {
codecTypes: TypeExist; // `import {} from '@pezkuwi/types/codec`
extrinsicTypes: TypeExist; // `import {} from '@pezkuwi/types/extrinsic`
genericTypes: TypeExist; // `import {} from '@pezkuwi/types/generic`
ignoredTypes: string[]; // No need to import these types
localTypes: TypeExistMap; // `import {} from '../something'`
lookupTypes: TypeExistMap; // `import {} from '@pezkuwi/types/lookup`
primitiveTypes: TypeExist; // `import {} from '@pezkuwi/types/primitive`
metadataTypes: TypeExist; // `import {} from '@pezkuwi/types/metadata`
typesTypes: TypeExist; // `import {} from '@pezkuwi/types/types`
definitions: Record<string, ModuleTypes>; // all definitions
typeToModule: Record<string, string>;
}
// returns the top-level types in the alternatives list, taking into account nested [], <> and () items
// E.g. for the string '[a<b,c>, d | e]' we return the array ['a<b,c>', 'd', 'e']
function splitAlternatives (type: string): string[] {
const alternatives = [];
let beginOfAlternative = 1;
let level = 0;
// we assume that the string starts with '['
for (let i = 1, count = type.length; i < count; i++) {
if (level === 0) {
switch (type[i]) {
case ']':
case ',':
case '|':
alternatives.push(type.substring(beginOfAlternative, i).trim());
beginOfAlternative = i + 1;
break;
}
}
switch (type[i]) {
case '[':
case '(':
case '<':
level++;
break;
case ']':
case ')':
case '>':
level--;
break;
}
}
return alternatives;
}
// Maps the types as found to the source location. This is used to generate the
// imports in the output file, dep-duped and sorted
/** @internal */
export function setImports (allDefs: Record<string, ModuleTypes>, imports: TypeImports, types: (string | null | undefined)[]): void {
const { codecTypes, extrinsicTypes, genericTypes, ignoredTypes, localTypes, metadataTypes, primitiveTypes, typesTypes } = imports;
types.filter((t): t is string => !!t).forEach((type): void => {
if (ignoredTypes.includes(type)) {
// do nothing
} else if (['AnyNumber', 'CallFunction', 'Codec', 'IExtrinsic', 'IMethod', 'ITuple'].includes(type)) {
typesTypes[type] = true;
} else if (['Metadata', 'PortableRegistry'].includes(type)) {
metadataTypes[type] = true;
} else if ((codecClasses as Record<string, unknown>)[type]) {
codecTypes[type] = true;
} else if ((extrinsicClasses as Record<string, unknown>)[type]) {
extrinsicTypes[type] = true;
} else if ((genericClasses as Record<string, unknown>)[type]) {
genericTypes[type] = true;
} else if ((primitiveClasses as Record<string, unknown>)[type]) {
primitiveTypes[type] = true;
} else if (type.startsWith('[') && type.includes('|')) {
const splitTypes = splitAlternatives(type);
setImports(allDefs, imports, splitTypes);
} else if (type.includes('<') || type.includes('(') || type.includes('[')) {
// If the type is a bit special (tuple, fixed u8, nested type...), then we
// need to parse it with `getTypeDef`.
const typeDef = getTypeDef(type);
setImports(allDefs, imports, [TypeDefInfo[typeDef.info]]);
// TypeDef.sub is a `TypeDef | TypeDef[]`
if (Array.isArray(typeDef.sub)) {
typeDef.sub.forEach((subType) => setImports(allDefs, imports, [subType.lookupName || subType.type]));
} else if (typeDef.sub && (typeDef.info !== TypeDefInfo.VecFixed || typeDef.sub.type !== 'u8')) {
// typeDef.sub is a TypeDef in this case
setImports(allDefs, imports, [typeDef.sub.lookupName || typeDef.sub.type]);
}
} else {
// find this module inside the exports from the rest
const [moduleName] = Object.entries(allDefs).find(([, { types }]): boolean =>
Object.keys(types).includes(type)
) || [null];
if (moduleName) {
localTypes[moduleName][type] = true;
}
}
});
}
// Create an Imports object, can be pre-filled with `ignoredTypes`
/** @internal */
export function createImports (importDefinitions: Record<string, Record<string, ModuleTypes>>, { types }: ModuleTypes = { types: {} }): TypeImports {
const definitions = {} as Record<string, ModuleTypes>;
const typeToModule = {} as Record<string, string>;
Object.entries(importDefinitions).forEach(([packagePath, packageDef]): void => {
Object.entries(packageDef).forEach(([name, moduleDef]): void => {
const fullName = `${packagePath}/${name}`;
definitions[fullName] = moduleDef;
Object.keys(moduleDef.types).forEach((type): void => {
if (typeToModule[type]) {
console.warn(`\t\tWARN: Overwriting duplicated type '${type}' ${typeToModule[type]} -> ${fullName}`);
}
typeToModule[type] = fullName;
});
});
});
return {
codecTypes: {},
definitions,
extrinsicTypes: {},
genericTypes: {},
ignoredTypes: Object.keys(types),
localTypes: Object.keys(definitions).reduce((local: Record<string, TypeExist>, mod): Record<string, TypeExist> => {
local[mod] = {};
return local;
}, {}),
lookupTypes: {},
metadataTypes: {},
primitiveTypes: {},
typeToModule,
typesTypes: {}
};
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './assert.js';
export * from './derived.js';
export * from './docs.js';
export * from './file.js';
export * from './formatting.js';
export * from './imports.js';
export * from './initMeta.js';
export * from './register.js';
export * from './wsMeta.js';
interface Cmp { name: { toString(): string } }
export function compareName (a: Cmp, b: Cmp): number {
return a.name.toString().localeCompare(b.name.toString());
}
// Rebrand type names from Polkadot SDK to Pezkuwi SDK
export function rebrandTypeName (typeName: string): string {
return typeName
// Main projects
.replace(/Polkadot/g, 'Pezkuwi')
.replace(/Substrate/g, 'Bizinikiwi')
.replace(/Cumulus/g, 'Pezcumulus')
// Networks
.replace(/Kusama/g, 'Dicle')
.replace(/Westend/g, 'Zagros')
.replace(/Rococo/g, 'Pezkuwichain')
// Chain types
.replace(/Parachain/g, 'Teyrchain')
// Module prefixes (in PascalCase type names)
.replace(/Frame(?=[A-Z])/g, 'Pezframe')
.replace(/Pallet(?=[A-Z])/g, 'Pezpallet')
// Primitives prefixes (sp_ becomes Sp in type names)
.replace(/^Sp(?=[A-Z])/g, 'Pezsp')
.replace(/([^a-zA-Z])Sp(?=[A-Z])/g, '$1Pezsp')
// Client prefixes (sc_ becomes Sc in type names)
.replace(/^Sc(?=[A-Z])/g, 'Pezsc')
.replace(/([^a-zA-Z])Sc(?=[A-Z])/g, '$1Pezsc');
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@pezkuwi/util/types';
import type { ExtraTypes } from '../generate/types.js';
import { Metadata, TypeRegistry } from '@pezkuwi/types';
import { registerDefinitions } from './register.js';
interface Result {
metadata: Metadata;
registry: TypeRegistry;
}
/**
* This helper method has been transitioned to work with V14, V15 and up.
*/
export function initMeta (staticMeta: HexString, extraTypes: ExtraTypes = {}): Result {
const registry = new TypeRegistry();
registerDefinitions(registry, extraTypes);
let metadata: Metadata;
try {
const opaqueMetadata = registry.createType('Option<OpaqueMetadata>', registry.createType('Raw', staticMeta).toU8a()).unwrap();
metadata = new Metadata(registry, opaqueMetadata.toHex());
} catch {
metadata = new Metadata(registry, staticMeta);
}
registry.setMetadata(metadata);
return { metadata, registry };
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TypeRegistry } from '@pezkuwi/types/create';
export function registerDefinitions (registry: TypeRegistry, extras: Record<string, Record<string, { types: Record<string, any> }>>): void {
Object.values(extras).forEach((def): void => {
Object.values(def).forEach(({ types }): void => {
registry.register(types);
});
});
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@pezkuwi/util/types';
import { promiseTracker } from '@pezkuwi/api/promise/decorateMethod';
import { TypeRegistry } from '@pezkuwi/types';
import { stringify, u8aToHex } from '@pezkuwi/util';
import { WebSocket } from '@pezkuwi/x-ws';
async function getWsData <T> (endpoint: string, method: 'rpc_methods' | 'state_call' | 'state_getMetadata' | 'state_getRuntimeVersion', params?: string[]): Promise<T> {
return new Promise((resolve, reject): void => {
const tracker = promiseTracker<T>(resolve, reject);
try {
const websocket = new WebSocket(endpoint);
websocket.onclose = (event: { code: number; reason: string }): void => {
if (event.code !== 1000) {
tracker.reject(new Error(`disconnected, code: '${event.code}' reason: '${event.reason}'`));
}
};
websocket.onerror = (event: unknown): void => {
tracker.reject(new Error(`WebSocket error:: ${stringify(event)}`));
};
websocket.onopen = (): void => {
console.log('connected');
params
? websocket.send(`{"id":"1","jsonrpc":"2.0","method":"${method}","params":[${params.map((param) => `"${param}"`).join(',')}]}`)
: websocket.send(`{"id":"1","jsonrpc":"2.0","method":"${method}","params":[]}`);
};
websocket.onmessage = (message: { data: string }): void => {
try {
tracker.resolve((JSON.parse(message.data) as { result: T }).result);
} catch (error) {
tracker.reject(error as Error);
}
websocket.close();
};
} catch (error) {
tracker.reject(error as Error);
}
});
}
export async function getMetadataViaWs (endpoint: string, metadataVer?: number): Promise<HexString> {
const registry = new TypeRegistry();
if (metadataVer) {
return await getWsData<HexString>(endpoint, 'state_call', ['Metadata_metadata_at_version', u8aToHex(registry.createType('u32', metadataVer).toU8a())]);
} else {
return await getWsData<HexString>(endpoint, 'state_getMetadata');
}
}
export async function getRpcMethodsViaWs (endpoint: string): Promise<string[]> {
const result = await getWsData<{ methods: string[] }>(endpoint, 'rpc_methods');
return result.methods;
}
export async function getRuntimeVersionViaWs (endpoint: string): Promise<[apiHash: string, apiVersion: number][]> {
const result = await getWsData<{ apis: [string, number][] }>(endpoint, 'state_getRuntimeVersion');
return result.apis;
}
+28
View File
@@ -0,0 +1,28 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "..",
"outDir": "./build",
"rootDir": "./src"
},
"include": [
"src/**/*"
],
"exclude": [
"scripts/**/*",
"**/*.spec.ts",
"**/mod.ts"
],
"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" },
{ "path": "../types-augment/tsconfig.build.json" },
{ "path": "../types-codec/tsconfig.build.json" },
{ "path": "../types-create/tsconfig.build.json" },
{ "path": "../types-support/tsconfig.build.json" }
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "..",
"outDir": "./build",
"rootDir": "./scripts",
"emitDeclarationOnly": false,
"noEmit": true
},
"include": [
"scripts/**/*"
],
"exclude": [
"src/**/*"
],
"references": [
{ "path": "../typegen/tsconfig.build.json" }
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "..",
"outDir": "./build",
"rootDir": "./src",
"emitDeclarationOnly": false,
"noEmit": true
},
"include": [
"**/*.spec.ts"
],
"references": [
{ "path": "../typegen/tsconfig.build.json" },
{ "path": "../types/tsconfig.build.json" }
]
}