mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-10-25 04:55:03 +00:00
Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle
This commit is contained in:
commit
d5f038faea
1383 files changed
+1088018
No files matched your search
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { augmentObject } from './augmentObject.js';
|
||||
|
||||
describe('augmentObject', (): void => {
|
||||
let spy: ReturnType<typeof jest.spyOn>;
|
||||
|
||||
beforeEach((): void => {
|
||||
spy = jest.spyOn(console, 'warn');
|
||||
});
|
||||
|
||||
afterEach((): void => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('logs added/removed sections and methods', (): void => {
|
||||
augmentObject(
|
||||
'test',
|
||||
{ bar: { b: 1 }, foo: { d: 1, f: 1 }, new: { z: 1 } },
|
||||
{ bar: { a: 1, c: 1 }, baz: { a: 1 }, foo: { c: 1, f: 1 } }
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.stringMatching(/API\/AUGMENT/),
|
||||
'api.test: Found 1 added and 1 removed modules:\n\t added: new\n\tremoved: baz'
|
||||
);
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.stringMatching(/API\/AUGMENT/),
|
||||
'api.test: Found 2 added and 3 removed calls:\n\t added: bar.b, foo.d\n\tremoved: bar.a, bar.c, foo.c'
|
||||
);
|
||||
});
|
||||
|
||||
it('copies sections to the dest', (): void => {
|
||||
const src = { bar: { b: 1 }, foo: { a: 1 } };
|
||||
|
||||
expect(augmentObject('test', src, {})).toEqual(src);
|
||||
});
|
||||
|
||||
it('adds fields to existing sections', (): void => {
|
||||
const src = { bar: { b: 1 }, foo: { a: 1 } };
|
||||
const dst = { baz: { c: 1 }, foo: { b: 2 } };
|
||||
|
||||
expect(augmentObject('test', src, dst)).toEqual({
|
||||
bar: { b: 1 },
|
||||
baz: { c: 1 },
|
||||
foo: { a: 1, b: 2 }
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { lazyMethods, logger, objectClear } from '@pezkuwi/util';
|
||||
|
||||
type Sections <T> = Record<string, Methods<T>>;
|
||||
|
||||
type Methods <T> = Record<string, T>;
|
||||
|
||||
type StringsStrings = [string[], string[]];
|
||||
|
||||
const l = logger('api/augment');
|
||||
|
||||
function logLength (type: 'added' | 'removed', values: string[], and: string[] = []): string {
|
||||
return values.length
|
||||
? ` ${values.length} ${type}${and.length ? ' and' : ''}`
|
||||
: '';
|
||||
}
|
||||
|
||||
function logValues (type: 'added' | 'removed', values: string[]): string {
|
||||
return values.length
|
||||
? `\n\t${type.padStart(7)}: ${values.sort().join(', ')}`
|
||||
: '';
|
||||
}
|
||||
|
||||
// log details to console
|
||||
function warn (prefix: string, type: 'calls' | 'modules', [added, removed]: StringsStrings): void {
|
||||
if (added.length || removed.length) {
|
||||
l.warn(`api.${prefix}: Found${logLength('added', added, removed)}${logLength('removed', removed)} ${type}:${logValues('added', added)}${logValues('removed', removed)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function findSectionExcludes (a: string[], b: string[]): string[] {
|
||||
return a.filter((s) => !b.includes(s));
|
||||
}
|
||||
|
||||
function findSectionIncludes (a: string[], b: string[]): string[] {
|
||||
return a.filter((s) => b.includes(s));
|
||||
}
|
||||
|
||||
function extractSections <T> (src: Sections<T>, dst: Sections<T>): StringsStrings {
|
||||
const srcSections = Object.keys(src);
|
||||
const dstSections = Object.keys(dst);
|
||||
|
||||
return [
|
||||
findSectionExcludes(srcSections, dstSections),
|
||||
findSectionExcludes(dstSections, srcSections)
|
||||
];
|
||||
}
|
||||
|
||||
function findMethodExcludes <T> (src: Sections<T>, dst: Sections<T>): string[] {
|
||||
const srcSections = Object.keys(src);
|
||||
const dstSections = findSectionIncludes(Object.keys(dst), srcSections);
|
||||
const excludes: string[] = [];
|
||||
|
||||
for (let s = 0, scount = dstSections.length; s < scount; s++) {
|
||||
const section = dstSections[s];
|
||||
const srcMethods = Object.keys(src[section]);
|
||||
const dstMethods = Object.keys(dst[section]);
|
||||
|
||||
for (let d = 0, mcount = dstMethods.length; d < mcount; d++) {
|
||||
const method = dstMethods[d];
|
||||
|
||||
if (!srcMethods.includes(method)) {
|
||||
excludes.push(`${section}.${method}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return excludes;
|
||||
}
|
||||
|
||||
function extractMethods <T> (src: Sections<T>, dst: Sections<T>): StringsStrings {
|
||||
return [
|
||||
findMethodExcludes(dst, src),
|
||||
findMethodExcludes(src, dst)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Takes a decorated api section (e.g. api.tx) and augment it with the details. It does not override what is
|
||||
* already available, but rather just adds new missing items into the result object.
|
||||
* @internal
|
||||
*/
|
||||
export function augmentObject <T> (prefix: string | null, src: Sections<T>, dst: Sections<T>, fromEmpty = false): Sections<T> {
|
||||
fromEmpty && objectClear(dst);
|
||||
|
||||
// NOTE: This part is slightly problematic since it will get the
|
||||
// values for at least all the sections and the names of the methods
|
||||
// (Since methods won't be decorated before lazy, this _may_ be ok)
|
||||
if (prefix && Object.keys(dst).length) {
|
||||
warn(prefix, 'modules', extractSections(src, dst));
|
||||
warn(prefix, 'calls', extractMethods(src, dst));
|
||||
}
|
||||
|
||||
const sections = Object.keys(src);
|
||||
|
||||
for (let i = 0, count = sections.length; i < count; i++) {
|
||||
const section = sections[i];
|
||||
const methods = src[section];
|
||||
|
||||
// We don't set here with a lazy interface, we decorate based
|
||||
// on the top-level structure (this bypasses adding lazy onto lazy)
|
||||
if (!dst[section]) {
|
||||
dst[section] = {};
|
||||
}
|
||||
|
||||
lazyMethods(dst[section], Object.keys(methods), (m: string) => methods[m]);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ExactDerive } from '@pezkuwi/api-derive';
|
||||
import type { AnyFunction } from '@pezkuwi/types/types';
|
||||
import type { ApiTypes, DecorateMethod, MethodResult } from '../types/index.js';
|
||||
|
||||
import { lazyDeriveSection } from '@pezkuwi/api-derive';
|
||||
|
||||
type AnyDeriveSection = Record<string, AnyFunction>;
|
||||
|
||||
// Most generic typings for `api.derive.*.*`
|
||||
type AnyDerive = Record<string, AnyDeriveSection>;
|
||||
|
||||
// Exact typings for a particular section `api.derive.section.*`
|
||||
type DeriveSection<ApiType extends ApiTypes, Section extends AnyDeriveSection> = {
|
||||
[M in keyof Section]: MethodResult<ApiType, Section[M]>
|
||||
};
|
||||
|
||||
// Exact typings for all sections `api.derive.*.*`
|
||||
export type AllDerives<ApiType extends ApiTypes> = {
|
||||
[S in keyof ExactDerive]: DeriveSection<ApiType, ExactDerive[S]>
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a section decorator which keeps all type information.
|
||||
*/
|
||||
export function decorateDeriveSections<ApiType extends ApiTypes> (decorateMethod: DecorateMethod<ApiType>, derives: AnyDerive): AllDerives<ApiType> {
|
||||
const getKeys = (s: string) =>
|
||||
Object.keys(derives[s]);
|
||||
|
||||
const creator = (s: string, m: string) =>
|
||||
decorateMethod(derives[s][m]) as AnyFunction;
|
||||
|
||||
const result: AnyDerive = {};
|
||||
const names = Object.keys(derives);
|
||||
|
||||
for (let i = 0, count = names.length; i < count; i++) {
|
||||
lazyDeriveSection(result, names[i], getKeys, creator);
|
||||
}
|
||||
|
||||
return result as AllDerives<ApiType>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BlockNumber, EventRecord, ExtrinsicStatus, H256, SignedBlock } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import { isCompact } from '@pezkuwi/util';
|
||||
|
||||
import { l } from './logging.js';
|
||||
|
||||
export function filterEvents (txHash: H256, { block: { extrinsics, header } }: SignedBlock, allEvents: EventRecord[], status: ExtrinsicStatus): { events?: EventRecord[], txIndex?: number, blockNumber?: BlockNumber } {
|
||||
// extrinsics to hashes
|
||||
for (const [txIndex, x] of extrinsics.entries()) {
|
||||
if (x.hash.eq(txHash)) {
|
||||
return {
|
||||
blockNumber: isCompact<BlockNumber>(header.number) ? header.number.unwrap() : header.number,
|
||||
events: allEvents.filter(({ phase }) =>
|
||||
phase.isApplyExtrinsic &&
|
||||
phase.asApplyExtrinsic.eqn(txIndex)
|
||||
),
|
||||
txIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// if we do get the block after finalized, it _should_ be there
|
||||
// only warn on filtering with isInBlock (finalization finalizes after)
|
||||
if (status.isInBlock) {
|
||||
const allHashes = extrinsics.map((x) => x.hash.toHex());
|
||||
|
||||
l.warn(`block ${header.hash.toHex()}: Unable to find extrinsic ${txHash.toHex()} inside ${allHashes.join(', ')}`);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all named
|
||||
export { filterEvents } from './filterEvents.js';
|
||||
export { isKeyringPair } from './isKeyringPair.js';
|
||||
export { l } from './logging.js';
|
||||
|
||||
// all starred
|
||||
export * from './decorate.js';
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId, Address } from '@pezkuwi/types/interfaces';
|
||||
import type { IKeyringPair } from '@pezkuwi/types/types';
|
||||
|
||||
import { isFunction } from '@pezkuwi/util';
|
||||
|
||||
export function isKeyringPair (account: string | IKeyringPair | AccountId | Address): account is IKeyringPair {
|
||||
return isFunction((account as IKeyringPair).sign);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { logger } from '@pezkuwi/util';
|
||||
|
||||
export const l = /*#__PURE__*/ logger('api/util');
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import { decorateStorage, Metadata, TypeRegistry } from '@pezkuwi/types';
|
||||
import metaStatic from '@pezkuwi/types-support/metadata/static-bizinikiwi';
|
||||
|
||||
import { extractStorageArgs } from './validate.js';
|
||||
|
||||
describe('extractStorageArgs', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
const metadata = new Metadata(registry, metaStatic);
|
||||
|
||||
registry.setMetadata(metadata);
|
||||
|
||||
const storage = decorateStorage(registry, metadata.asLatest, metadata.version);
|
||||
|
||||
it('validates no-arg plain', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(registry, storage['timestamp']['now'], [])
|
||||
).toEqual([storage['timestamp']['now'], []]);
|
||||
});
|
||||
|
||||
it('validates no-arg plain (with undefined, undefined)', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(registry, storage['timestamp']['now'], [undefined, undefined])
|
||||
).toEqual([storage['timestamp']['now'], []]);
|
||||
});
|
||||
|
||||
it('validates no-arg plain (failing when there are args)', (): void => {
|
||||
expect(
|
||||
() => extractStorageArgs(registry, storage['timestamp']['now'], [123, 456])
|
||||
).toThrow('timestamp.now() does not take any arguments, 2 found');
|
||||
});
|
||||
|
||||
it('validates map, 1 arg', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(registry, storage['staking']['payee'], ['abc'])
|
||||
).toEqual([storage['staking']['payee'], ['abc']]);
|
||||
});
|
||||
|
||||
it('validates map, 1 arg (failing with no args)', (): void => {
|
||||
expect(
|
||||
() => extractStorageArgs(registry, storage['staking']['payee'], [])
|
||||
).toThrow('staking.payee(AccountId32) is a map, requiring 1 arguments, 0 found');
|
||||
});
|
||||
|
||||
it('validates map, 1 arg (failing with more args)', (): void => {
|
||||
expect(
|
||||
() => extractStorageArgs(registry, storage['staking']['payee'], ['abc', 'def'])
|
||||
).toThrow('staking.payee(AccountId32) is a map, requiring 1 arguments, 2 found');
|
||||
});
|
||||
|
||||
it('validates doublemap, 2 args', (): void => {
|
||||
expect(
|
||||
extractStorageArgs(registry, storage['staking']['erasStakers'], [1, '0x1234'])
|
||||
).toEqual([storage['staking']['erasStakers'], [1, '0x1234']]);
|
||||
});
|
||||
|
||||
it('validates doublemap, 2 args (failing with no args)', (): void => {
|
||||
expect(
|
||||
() => extractStorageArgs(registry, storage['staking']['erasStakers'], [])
|
||||
).toThrow('staking.erasStakers(u32, AccountId32) is a map, requiring 2 arguments, 0 found');
|
||||
});
|
||||
|
||||
it('validates doublemap, 2 args (failing with 1 arg)', (): void => {
|
||||
expect(
|
||||
() => extractStorageArgs(registry, storage['staking']['erasStakers'], [123])
|
||||
).toThrow('staking.erasStakers(u32, AccountId32) is a map, requiring 2 arguments, 1 found');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2025 @pezkuwi/api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SiLookupTypeId } from '@pezkuwi/types/interfaces';
|
||||
import type { StorageEntry } from '@pezkuwi/types/primitive/types';
|
||||
import type { Registry } from '@pezkuwi/types/types';
|
||||
|
||||
import { isUndefined } from '@pezkuwi/util';
|
||||
|
||||
function sig ({ lookup }: Registry, { method, section }: StorageEntry, args: SiLookupTypeId[]): string {
|
||||
return `${section}.${method}(${args.map((a) => lookup.getTypeDef(a).type).join(', ')})`;
|
||||
}
|
||||
|
||||
// sets up the arguments in the form of [creator, args] ready to be used in a storage
|
||||
// call. Additionally, it verifies that the correct number of arguments have been passed
|
||||
export function extractStorageArgs (registry: Registry, creator: StorageEntry, _args: unknown[]): [StorageEntry, unknown[]] {
|
||||
const args = _args.filter((a) => !isUndefined(a));
|
||||
|
||||
if (creator.meta.type.isPlain) {
|
||||
if (args.length !== 0) {
|
||||
throw new Error(`${sig(registry, creator, [])} does not take any arguments, ${args.length} found`);
|
||||
}
|
||||
} else {
|
||||
const { hashers, key } = creator.meta.type.asMap;
|
||||
const keys = hashers.length === 1
|
||||
? [key]
|
||||
: registry.lookup.getSiType(key).def.asTuple.map((t) => t);
|
||||
|
||||
if (args.length !== keys.length) {
|
||||
throw new Error(`${sig(registry, creator, keys)} is a map, requiring ${keys.length} arguments, ${args.length} found`);
|
||||
}
|
||||
}
|
||||
|
||||
// pass as tuple
|
||||
return [creator, args];
|
||||
}
|
||||
Reference in new issue
Block a user