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
+9
View File
@@ -0,0 +1,9 @@
// Copyright 2017-2025 @pezkuwi/types-codec authors & contributors
// SPDX-License-Identifier: Apache-2.0
// all named
export { packageInfo } from './packageInfo.js';
export { TypeDefInfo } from './types/index.js';
// all starred
export * from './exports.js';
+257
View File
@@ -0,0 +1,257 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Codec, CodecClass, LookupString, Registry, U8aBitLength, UIntBitLength } from '@pezkuwi/types-codec/types';
import type { TypeDef } from '../types/index.js';
import { BTreeMap, BTreeSet, Bytes, CodecSet, Compact, DoNotConstruct, Enum, HashMap, Int, Null, Option, Range, RangeInclusive, Result, Struct, Tuple, U8aFixed, UInt, Vec, VecFixed, WrapperKeepOpaque, WrapperOpaque } from '@pezkuwi/types-codec';
import { isNumber, stringify } from '@pezkuwi/util';
import { TypeDefInfo } from '../types/index.js';
import { getTypeDef } from '../util/getTypeDef.js';
function getTypeDefType ({ lookupName, type }: TypeDef): string {
return lookupName || type;
}
function getSubDefArray (value: TypeDef): TypeDef[] {
if (!Array.isArray(value.sub)) {
throw new Error(`Expected subtype as TypeDef[] in ${stringify(value)}`);
}
return value.sub;
}
function getSubDef (value: TypeDef): TypeDef {
if (!value.sub || Array.isArray(value.sub)) {
throw new Error(`Expected subtype as TypeDef in ${stringify(value)}`);
}
return value.sub;
}
function getSubType (value: TypeDef): string {
return getTypeDefType(getSubDef(value));
}
// create a maps of type string CodecClasss from the input
function getTypeClassMap (value: TypeDef): Record<string, string> {
const subs = getSubDefArray(value);
const map: Record<string, string> = {};
for (let i = 0, count = subs.length; i < count; i++) {
const sub = subs[i];
if (!sub.name) {
throw new Error(`No name found in definition ${stringify(sub)}`);
}
map[sub.name] = getTypeDefType(sub);
}
return map;
}
// create an array of type string CodecClasss from the input
function getTypeClassArray (value: TypeDef): string[] {
return getSubDefArray(value).map(getTypeDefType);
}
function createInt (Clazz: typeof Int | typeof UInt, { displayName, length }: TypeDef): CodecClass<Codec> {
if (!isNumber(length)) {
throw new Error(`Expected bitLength information for ${displayName || Clazz.constructor.name}<bitLength>`);
}
return Clazz.with(length as UIntBitLength, displayName);
}
function createHashMap (Clazz: typeof BTreeMap | typeof HashMap, value: TypeDef): CodecClass<Codec> {
const [keyType, valueType] = getTypeClassArray(value);
return Clazz.with(keyType, valueType);
}
function createWithSub (Clazz: { with: (t: string) => CodecClass<Codec> }, value: TypeDef): CodecClass<Codec> {
return Clazz.with(getSubType(value));
}
const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => CodecClass<Codec>> = {
[TypeDefInfo.BTreeMap]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createHashMap(BTreeMap, value),
[TypeDefInfo.BTreeSet]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(BTreeSet, value),
[TypeDefInfo.Compact]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(Compact, value),
[TypeDefInfo.DoNotConstruct]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
DoNotConstruct.with(value.displayName || value.type),
[TypeDefInfo.Enum]: (_registry: Registry, value: TypeDef): CodecClass<Codec> => {
const subs = getSubDefArray(value);
return Enum.with(
subs.every(({ type }) => type === 'Null')
? subs.reduce<Record<string, number>>((out, { index, name }, count) => {
if (!name) {
throw new Error('No name found in sub definition');
}
out[name] = index || count;
return out;
}, {})
: getTypeClassMap(value)
);
},
[TypeDefInfo.HashMap]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createHashMap(HashMap, value),
[TypeDefInfo.Int]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createInt(Int, value),
// We have circular deps between Linkage & Struct
[TypeDefInfo.Linkage]: (_registry: Registry, value: TypeDef): CodecClass<Codec> => {
const type = `Option<${getSubType(value)}>`;
// eslint-disable-next-line sort-keys
const Clazz = Struct.with({ previous: type, next: type } as any);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Clazz.prototype.toRawType = function (): string {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
return `Linkage<${this.next.toRawType(true)}>`;
};
return Clazz;
},
[TypeDefInfo.Null]: (_registry: Registry, _value: TypeDef): CodecClass<Codec> =>
Null,
[TypeDefInfo.Option]: (_registry: Registry, value: TypeDef): CodecClass<Codec> => {
if (!value.sub || Array.isArray(value.sub)) {
throw new Error('Expected type information for Option');
}
// NOTE This is opt-in (unhandled), not by default
// if (value.sub.type === 'bool') {
// return OptionBool;
// }
return createWithSub(Option, value);
},
[TypeDefInfo.Plain]: (registry: Registry, value: TypeDef): CodecClass<Codec> =>
registry.getOrUnknown(value.type),
[TypeDefInfo.Range]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(Range, value),
[TypeDefInfo.RangeInclusive]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(RangeInclusive, value),
[TypeDefInfo.Result]: (_registry: Registry, value: TypeDef): CodecClass<Codec> => {
const [Ok, Err] = getTypeClassArray(value);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return Result.with({ Err, Ok });
},
[TypeDefInfo.Set]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
CodecSet.with(
getSubDefArray(value).reduce<Record<string, number>>((result, { index, name }) => {
if (!name || !isNumber(index)) {
throw new Error('No name found in sub definition');
}
result[name] = index;
return result;
}, {}),
value.length
),
[TypeDefInfo.Si]: (registry: Registry, value: TypeDef): CodecClass<Codec> =>
getTypeClass(registry, registry.lookup.getTypeDef(value.type as LookupString)),
[TypeDefInfo.Struct]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
Struct.with(getTypeClassMap(value), value.alias),
[TypeDefInfo.Tuple]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
Tuple.with(getTypeClassArray(value)),
[TypeDefInfo.UInt]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createInt(UInt, value),
[TypeDefInfo.Vec]: (_registry: Registry, { sub }: TypeDef): CodecClass<Codec> => {
if (!sub || Array.isArray(sub)) {
throw new Error('Expected type information for vector');
}
return (
sub.type === 'u8'
? Bytes
: Vec.with(getTypeDefType(sub))
);
},
[TypeDefInfo.VecFixed]: (_registry: Registry, { displayName, length, sub }: TypeDef): CodecClass<Codec> => {
if (!isNumber(length) || !sub || Array.isArray(sub)) {
throw new Error('Expected length & type information for fixed vector');
}
return (
sub.type === 'u8'
? U8aFixed.with((length * 8) as U8aBitLength, displayName)
: VecFixed.with(getTypeDefType(sub), length)
);
},
[TypeDefInfo.WrapperKeepOpaque]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(WrapperKeepOpaque, value),
[TypeDefInfo.WrapperOpaque]: (_registry: Registry, value: TypeDef): CodecClass<Codec> =>
createWithSub(WrapperOpaque, value)
};
export function constructTypeClass<T extends Codec = Codec> (registry: Registry, typeDef: TypeDef): CodecClass<T> {
try {
const Type = infoMapping[typeDef.info](registry, typeDef);
if (!Type) {
throw new Error('No class created');
}
// don't clobber any existing
if (!Type.__fallbackType && typeDef.fallbackType) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ...this is the only place we we actually assign this...
Type.__fallbackType = typeDef.fallbackType;
}
return Type as CodecClass<T>;
} catch (error) {
throw new Error(`Unable to construct class from ${stringify(typeDef)}: ${(error as Error).message}`);
}
}
// Returns the type Class for construction
export function getTypeClass<T extends Codec = Codec> (registry: Registry, typeDef: TypeDef): CodecClass<T> {
return registry.getUnsafe(typeDef.type, false, typeDef) as unknown as CodecClass<T>;
}
export function createClassUnsafe<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K): CodecClass<T> {
return (
// just retrieve via name, no creation via typeDef
registry.getUnsafe(type) ||
// we don't have an existing type, create the class via typeDef
getTypeClass(
registry,
registry.isLookupType(type)
? registry.lookup.getTypeDef(type)
: getTypeDef(type)
)
);
}
@@ -0,0 +1,5 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './class.js';
export * from './type.js';
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Bytes } from '@pezkuwi/types-codec';
import type { Codec, CodecClass, IU8a, Registry } from '@pezkuwi/types-codec/types';
import type { CreateOptions } from '../types/index.js';
import { Option } from '@pezkuwi/types-codec';
import { isHex, isU8a, u8aEq, u8aToHex, u8aToU8a } from '@pezkuwi/util';
import { createClassUnsafe } from './class.js';
// With isPedantic, actually check that the encoding matches that supplied. This
// is much slower, but verifies that we have the correct types defined
function checkInstance (created: Codec, matcher: Uint8Array): void {
const u8a = created.toU8a();
const rawType = created.toRawType();
const isOk = (
// full match, all ok
u8aEq(u8a, matcher) ||
(
// on a length-prefixed type, just check the actual length
['Bytes', 'Text', 'Type'].includes(rawType) &&
matcher.length === (created as unknown as Bytes).length
) ||
(
// when the created is empty and matcher is also empty, let it slide...
created.isEmpty &&
matcher.every((v) => !v)
)
);
if (!isOk) {
throw new Error(`${rawType}:: Decoded input doesn't match input, received ${u8aToHex(matcher, 512)} (${matcher.length} bytes), created ${u8aToHex(u8a, 512)} (${u8a.length} bytes)`);
}
}
function checkPedantic (created: Codec, [value]: unknown[]): void {
if (isU8a(value)) {
checkInstance(created, value);
} else if (isHex(value)) {
checkInstance(created, u8aToU8a(value));
}
}
// Initializes a type with a value. This also checks for fallbacks and in the cases
// where isPedantic is specified (storage decoding), also check the format/structure
function initType<T extends Codec> (registry: Registry, Type: CodecClass, params: unknown[] = [], { blockHash, isFallback, isOptional, isPedantic }: CreateOptions = {}): T {
const created = new (
isOptional
? Option.with(Type)
: Type
)(registry, ...params);
isPedantic && checkPedantic(created, params);
if (blockHash) {
created.createdAtHash = createTypeUnsafe<IU8a>(registry, 'BlockHash', [blockHash]);
}
if (isFallback) {
created.isStorageFallback = true;
}
return created as T;
}
// An unsafe version of the `createType` below. It's unsafe because the `type`
// argument here can be any string, which, when it cannot parse, will yield a
// runtime error.
export function createTypeUnsafe<T extends Codec = Codec, K extends string = string> (registry: Registry, type: K, params: unknown[] = [], options: CreateOptions = {}): T {
let Clazz: CodecClass | null = null;
let firstError: Error | null = null;
try {
Clazz = createClassUnsafe(registry, type);
return initType(registry, Clazz, params, options);
} catch (error) {
firstError = new Error(`createType(${type}):: ${(error as Error).message}`);
}
if (Clazz?.__fallbackType) {
try {
Clazz = createClassUnsafe(registry, Clazz.__fallbackType as unknown as K);
return initType(registry, Clazz, params, options);
} catch {
// swallow, we will throw the first error again
}
}
throw firstError;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @pezkuwi/types-codec authors & contributors
// SPDX-License-Identifier: Apache-2.0
// all starred
export * from './create/index.js';
export * from './util/index.js';
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './packageDetect.js';
export * from './bundle.js';
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './index.js';
@@ -0,0 +1,12 @@
// Copyright 2017-2026 @pezkuwi/types-create 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 codecInfo } from '@pezkuwi/types-codec/packageInfo';
import { detectPackage } from '@pezkuwi/util';
import { packageInfo } from './packageInfo.js';
detectPackage(packageInfo, null, [codecInfo]);
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2026 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Do not edit, auto-generated by @pezkuwi/dev
export const packageInfo = { name: '@pezkuwi/types-create', path: 'auto', type: 'auto', version: '16.5.4' };
@@ -0,0 +1,19 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
// import type lookup before we augment - in some environments
// this is required to allow for ambient/previous definitions
import '@pezkuwi/types-codec/types/registry';
import type { Codec, CodecClass, ICompact, INumber, LookupString } from '@pezkuwi/types-codec/types';
import type { ILookup, TypeDef } from '@pezkuwi/types-create/types';
declare module '@pezkuwi/types-codec/types/registry' {
interface Registry {
readonly lookup: ILookup;
createLookupType (lookupId: ICompact<INumber> | number): LookupString;
getUnsafe <T extends Codec = Codec, K extends string = string> (name: K, withUnknown?: boolean, knownTypeDef?: TypeDef): CodecClass<T> | undefined;
setLookup (lookup: ILookup): void;
}
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './augmentRegistry.js';
// all types
export type { CodecCreateOptions as CreateOptions } from '@pezkuwi/types-codec/types';
// all starred
export * from './lookup.js';
export * from './types.js';
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option, Text } from '@pezkuwi/types-codec';
import type { ICompact, INumber, LookupString } from '@pezkuwi/types-codec/types';
import type { TypeDef } from './types.js';
// A simplified SiType without the need for an interface import
// (while type interfaces are still in @pezkuwi/types). This provides
// the minimum interface allowing us to work with it here
interface SiTypeBase {
def: {
asTuple: ICompact<INumber>[]
}
}
export interface ILookup {
getSiType (lookupId: ICompact<INumber> | LookupString | number): SiTypeBase;
getTypeDef (lookupId: ICompact<INumber> | LookupString | number): TypeDef;
sanitizeField (name: Option<Text>): [string | null, string | null];
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
export enum TypeDefInfo {
BTreeMap,
BTreeSet,
Compact,
DoNotConstruct,
Enum,
HashMap,
Int,
Linkage,
Null,
Option,
Plain,
Range,
RangeInclusive,
Result,
Set,
Si,
Struct,
Tuple,
UInt,
Vec,
VecFixed,
WrapperKeepOpaque,
WrapperOpaque
}
export interface TypeDef {
alias?: Map<string, string> | undefined;
displayName?: string | undefined;
docs?: string[] | undefined;
fallbackType?: string | undefined;
info: TypeDefInfo;
index?: number;
isFromSi?: boolean;
length?: number;
lookupIndex?: number;
lookupName?: string | undefined;
lookupNameRoot?: string | undefined;
name?: string | undefined;
namespace?: string | undefined;
sub?: TypeDef | TypeDef[];
type: string;
typeName?: string | undefined;
}
@@ -0,0 +1,217 @@
// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types';
import { encodeTypeDef, TypeDefInfo } from '@pezkuwi/types-create';
describe('encodeTypeDef', (): void => {
const registry = new TypeRegistry();
it('correctly encodes a complex struct', (): void => {
expect(
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Struct,
sub: [
{
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
info: TypeDefInfo.Struct,
name: 'b',
sub: [
{
info: TypeDefInfo.Plain,
name: 'c',
type: 'u32'
},
{
info: TypeDefInfo.Vec,
name: 'd',
sub: {
info: TypeDefInfo.Plain,
type: 'u32'
},
type: ''
}
],
type: ''
}
],
type: ''
}))
).toEqual({
a: 'u32',
b: '{"c":"u32","d":"Vec<u32>"}'
});
});
it('correctly encodes a complex struct (named)', (): void => {
expect(
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Struct,
sub: [
{
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
info: TypeDefInfo.Struct,
name: 'b',
sub: [
{
info: TypeDefInfo.Plain,
name: 'c',
type: 'u32'
},
{
displayName: 'Something',
info: TypeDefInfo.Vec,
name: 'd',
sub: {
info: TypeDefInfo.Plain,
type: 'u32'
},
type: ''
}
],
type: ''
}
],
type: ''
}))
).toEqual({
a: 'u32',
b: '{"c":"u32","d":"Something"}'
});
});
it('correctly encodes a complex enum', (): void => {
expect(
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Enum,
sub: [
{
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
info: TypeDefInfo.Struct,
name: 'b',
sub: [
{
info: TypeDefInfo.Plain,
name: 'c',
type: 'u32'
},
{
info: TypeDefInfo.Vec,
name: 'd',
sub: {
info: TypeDefInfo.Plain,
type: 'u32'
},
type: ''
}
],
type: ''
},
{
info: TypeDefInfo.Enum,
name: 'f',
sub: [
{
info: TypeDefInfo.Plain,
name: 'g',
type: 'Null'
},
{
info: TypeDefInfo.Plain,
name: 'h',
type: 'Null'
}
],
type: ''
}
],
type: ''
}))
).toEqual({
_enum: {
a: 'u32',
b: '{"c":"u32","d":"Vec<u32>"}',
f: '{"_enum":["g","h"]}'
}
});
});
it('correctly encodes a complex enum (named)', (): void => {
expect(
JSON.parse(encodeTypeDef(registry, {
info: TypeDefInfo.Enum,
sub: [
{
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
displayName: 'Something',
info: TypeDefInfo.Struct,
name: 'b',
sub: [
{
info: TypeDefInfo.Plain,
name: 'c',
type: 'u32'
},
{
info: TypeDefInfo.Vec,
name: 'd',
sub: {
info: TypeDefInfo.Plain,
type: 'u32'
},
type: ''
}
],
type: ''
},
{
displayName: 'Option',
info: TypeDefInfo.Option,
name: 'e',
sub: {
displayName: 'Result',
info: TypeDefInfo.Result,
sub: [
{
info: TypeDefInfo.Null,
type: ''
},
{
info: TypeDefInfo.Plain,
type: 'u32'
}
],
type: ''
},
type: ''
}
],
type: ''
}))
).toEqual({
_enum: {
a: 'u32',
b: 'Something',
e: 'Option<Result<Null, u32>>'
}
});
});
});
@@ -0,0 +1,205 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registry } from '@pezkuwi/types-codec/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import { isNumber, isUndefined, objectSpread, stringify } from '@pezkuwi/util';
import { TypeDefInfo } from '../types/index.js';
interface ToString { toString: () => string }
const stringIdentity = <T extends ToString> (value: T): string => value.toString();
const INFO_WRAP = ['BTreeMap', 'BTreeSet', 'Compact', 'HashMap', 'Option', 'Result', 'Vec'];
export function paramsNotation <T extends ToString> (outer: string, inner?: T | T[], transform: (_: T) => string = stringIdentity): string {
return `${outer}${
inner
? `<${(Array.isArray(inner) ? inner : [inner]).map(transform).join(', ')}>`
: ''
}`;
}
function encodeWithParams (registry: Registry, typeDef: TypeDef, outer: string): string {
const { info, sub } = typeDef;
switch (info) {
case TypeDefInfo.BTreeMap:
case TypeDefInfo.BTreeSet:
case TypeDefInfo.Compact:
case TypeDefInfo.HashMap:
case TypeDefInfo.Linkage:
case TypeDefInfo.Option:
case TypeDefInfo.Range:
case TypeDefInfo.RangeInclusive:
case TypeDefInfo.Result:
case TypeDefInfo.Vec:
case TypeDefInfo.WrapperKeepOpaque:
case TypeDefInfo.WrapperOpaque:
return paramsNotation(outer, sub, (p) => encodeTypeDef(registry, p));
}
throw new Error(`Unable to encode ${stringify(typeDef)} with params`);
}
function encodeSubTypes (registry: Registry, sub: TypeDef[], asEnum?: boolean, extra?: Record<string, unknown>): string {
const names = sub.map(({ name }) => name);
if (!names.every((n) => !!n)) {
throw new Error(`Subtypes does not have consistent names, ${names.join(', ')}`);
}
const inner: Record<string, string> = objectSpread({}, extra);
for (let i = 0, count = sub.length; i < count; i++) {
const def = sub[i];
if (!def.name) {
throw new Error(`No name found in ${stringify(def)}`);
}
inner[def.name] = encodeTypeDef(registry, def);
}
return stringify(
asEnum
? { _enum: inner }
: inner
);
}
// We setup a record here to ensure we have comprehensive coverage (any item not covered will result
// in a compile-time error with the missing index)
const encoders: Record<TypeDefInfo, (registry: Registry, typeDef: TypeDef) => string> = {
[TypeDefInfo.BTreeMap]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'BTreeMap'),
[TypeDefInfo.BTreeSet]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'BTreeSet'),
[TypeDefInfo.Compact]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Compact'),
[TypeDefInfo.DoNotConstruct]: (registry: Registry, { displayName, lookupIndex, lookupName }: TypeDef) =>
`DoNotConstruct<${lookupName || displayName || (isUndefined(lookupIndex) ? 'Unknown' : registry.createLookupType(lookupIndex))}>`,
[TypeDefInfo.Enum]: (registry: Registry, { sub }: TypeDef): string => {
if (!Array.isArray(sub)) {
throw new Error('Unable to encode Enum type');
}
// c-like enums have all Null entries
// TODO We need to take the disciminant into account and auto-add empty entries
return sub.every(({ type }) => type === 'Null')
? stringify({ _enum: sub.map(({ name }, index) => `${name || `Empty${index}`}`) })
: encodeSubTypes(registry, sub, true);
},
[TypeDefInfo.HashMap]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'HashMap'),
[TypeDefInfo.Int]: (_registry: Registry, { length = 32 }: TypeDef) =>
`Int<${length}>`,
[TypeDefInfo.Linkage]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Linkage'),
[TypeDefInfo.Null]: (_registry: Registry, _typeDef: TypeDef) =>
'Null',
[TypeDefInfo.Option]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Option'),
[TypeDefInfo.Plain]: (_registry: Registry, { displayName, type }: TypeDef) =>
displayName || type,
[TypeDefInfo.Range]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Range'),
[TypeDefInfo.RangeInclusive]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'RangeInclusive'),
[TypeDefInfo.Result]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Result'),
[TypeDefInfo.Set]: (_registry: Registry, { length = 8, sub }: TypeDef): string => {
if (!Array.isArray(sub)) {
throw new Error('Unable to encode Set type');
}
return stringify({
_set: sub.reduce((all, { index, name }, count) =>
objectSpread(all, { [`${name || `Unknown${index || count}`}`]: index || count }),
{ _bitLength: length || 8 })
});
},
[TypeDefInfo.Si]: (_registry: Registry, { lookupName, type }: TypeDef) =>
lookupName || type,
[TypeDefInfo.Struct]: (registry: Registry, { alias, sub }: TypeDef): string => {
if (!Array.isArray(sub)) {
throw new Error('Unable to encode Struct type');
}
return encodeSubTypes(registry, sub, false, alias
? {
_alias: [...alias.entries()].reduce<Record<string, string>>((all, [k, v]) =>
objectSpread(all, { [k]: v }), {}
)
}
: {}
);
},
[TypeDefInfo.Tuple]: (registry: Registry, { sub }: TypeDef): string => {
if (!Array.isArray(sub)) {
throw new Error('Unable to encode Tuple type');
}
return `(${sub.map((type) => encodeTypeDef(registry, type)).join(',')})`;
},
[TypeDefInfo.UInt]: (_registry: Registry, { length = 32 }: TypeDef) =>
`UInt<${length}>`,
[TypeDefInfo.Vec]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'Vec'),
[TypeDefInfo.VecFixed]: (_registry: Registry, { length, sub }: TypeDef): string => {
if (!isNumber(length) || !sub || Array.isArray(sub)) {
throw new Error('Unable to encode VecFixed type');
}
return `[${sub.type};${length}]`;
},
[TypeDefInfo.WrapperKeepOpaque]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'WrapperKeepOpaque'),
[TypeDefInfo.WrapperOpaque]: (registry: Registry, typeDef: TypeDef) =>
encodeWithParams(registry, typeDef, 'WrapperOpaque')
};
function encodeType (registry: Registry, typeDef: TypeDef, withLookup = true): string {
return withLookup && typeDef.lookupName
? typeDef.lookupName
: encoders[typeDef.info](registry, typeDef);
}
export function encodeTypeDef (registry: Registry, typeDef: TypeDef): string {
// In the case of contracts we do have the unfortunate situation where the displayName would
// refer to "Option" when it is an option. For these, string it out, only using when actually
// not a top-level element to be used
return (typeDef.displayName && !INFO_WRAP.some((i) => typeDef.displayName === i))
? typeDef.displayName
: encodeType(registry, typeDef);
}
export function withTypeString (registry: Registry, typeDef: Omit<TypeDef, 'type'> & { type?: string }): TypeDef {
return objectSpread({}, typeDef, {
type: encodeType(registry, typeDef as TypeDef, false)
});
}
@@ -0,0 +1,704 @@
// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { TypeRegistry } from '@pezkuwi/types';
import { getTypeDef, TypeDefInfo } from '@pezkuwi/types-create';
import { stringify } from '@pezkuwi/util';
describe('getTypeDef', (): void => {
it('maps empty tuples to empty tuple', (): void => {
expect(
getTypeDef('()')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [],
type: '()'
});
});
it('properly decodes a BTreeMap<u32, Text>', (): void => {
expect(
getTypeDef('BTreeMap<u32, Text>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.BTreeMap,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Text'
}
],
type: 'BTreeMap<u32,Text>'
});
});
it('properly decodes a BTreeSet<Text>', (): void => {
expect(
getTypeDef('BTreeSet<Text>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.BTreeSet,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Text'
},
type: 'BTreeSet<Text>'
});
});
it('properly decodes a Result<u32, Text>', (): void => {
expect(
getTypeDef('Result<u32, Text>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Result,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Text'
}
],
type: 'Result<u32,Text>'
});
});
it('properly decodes a Result<Result<(), u32>, Text>', (): void => {
expect(
getTypeDef('Result<Result<Null,u32>,Text>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Result,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Result,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Null'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
}
],
type: 'Result<Null,u32>'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Text'
}
],
type: 'Result<Result<Null,u32>,Text>'
});
});
it('returns a type structure', (): void => {
expect(
getTypeDef('(u32, Compact<u32>, Vec<u64>, Option<u128>, (Text,Vec<(Bool,u128)>))')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Compact,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
type: 'Compact<u32>'
},
{
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u64'
},
type: 'Vec<u64>'
},
{
displayName: undefined,
info: TypeDefInfo.Option,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u128'
},
type: 'Option<u128>'
},
{
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Text'
},
{
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Bool'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u128'
}
],
type: '(Bool,u128)'
},
type: 'Vec<(Bool,u128)>'
}
],
type: '(Text,Vec<(Bool,u128)>)'
}
],
type: '(u32,Compact<u32>,Vec<u64>,Option<u128>,(Text,Vec<(Bool,u128)>))'
});
});
it('returns a type structure (sanitized)', (): void => {
expect(
getTypeDef('Vec<(Box<PropIndex>, Proposal,Lookup::Target)>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'PropIndex'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Proposal'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'LookupTarget'
}
],
type: '(PropIndex,Proposal,LookupTarget)'
},
type: 'Vec<(PropIndex,Proposal,LookupTarget)>'
});
});
it('returns a type structure (actual)', (): void => {
expect(
getTypeDef('Vec<(PropIndex, Proposal, AccountId)>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'PropIndex'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Proposal'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'AccountId'
}
],
type: '(PropIndex,Proposal,AccountId)'
},
type: 'Vec<(PropIndex,Proposal,AccountId)>'
});
});
it('returns an actual Struct', (): void => {
expect(
getTypeDef('{"balance":"Balance","account_id":"AccountId","log":"(u64, Signature)"}')
).toEqual({
alias: undefined,
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Struct,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'balance',
type: 'Balance'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'account_id',
type: 'AccountId'
},
{
displayName: undefined,
info: TypeDefInfo.Tuple,
name: 'log',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u64'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Signature'
}
],
type: '(u64,Signature)'
}
],
type: '{"balance":"Balance","account_id":"AccountId","log":"(u64,Signature)"}'
});
});
it('creates a nested fixed vec', (): void => {
expect(
getTypeDef('[[[bool; 3]; 6]; 9]')
).toEqual({
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 9,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 6,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 3,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
},
type: '[bool;3]'
},
type: '[[bool;3];6]'
},
type: '[[[bool;3];6];9]'
});
});
it('creates a nested fixed vec (named)', (): void => {
expect(
getTypeDef('[[bool; 6]; 3; MyType]')
).toEqual({
displayName: 'MyType',
info: TypeDefInfo.VecFixed,
length: 3,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 6,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
},
type: '[bool;6]'
},
type: '[[bool;6];3;MyType]'
});
});
it('creates a nested tuple', (): void => {
expect(
getTypeDef('((u32, u64), u128)')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Tuple,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u64'
}
],
type: '(u32,u64)'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u128'
}
],
type: '((u32,u64),u128)'
});
});
it('creates a nested enum with tuple/struct', (): void => {
expect(
getTypeDef(stringify({
_enum: {
A: 'u32',
B: '(u32, bool)',
C: {
d: 'AccountId',
e: 'Balance'
}
}
}))
).toEqual({
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Enum,
name: undefined,
sub: [
{
displayName: undefined,
index: 0,
info: TypeDefInfo.Plain,
name: 'A',
type: 'u32'
},
{
displayName: undefined,
index: 1,
info: TypeDefInfo.Tuple,
name: 'B',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
}
],
type: '(u32,bool)'
},
{
alias: undefined,
displayName: undefined,
fallbackType: undefined,
index: 2,
info: TypeDefInfo.Struct,
name: 'C',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'd',
type: 'AccountId'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'e',
type: 'Balance'
}
],
type: '{"d":"AccountId","e":"Balance"}'
}
],
type: '{"_enum":{"A":"u32","B":"(u32,bool)","C":{"d":"AccountId","e":"Balance"}}}'
});
});
it('creates a nested struct with struct/tuple', (): void => {
expect(
getTypeDef(stringify({
a: 'u32',
b: '(u32, bool)',
c: {
d: 'AccountId',
e: 'Balance'
}
}))
).toEqual({
alias: undefined,
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Struct,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Tuple,
name: 'b',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
}
],
type: '(u32,bool)'
},
{
alias: undefined,
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Struct,
name: 'c',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'd',
type: 'AccountId'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'e',
type: 'Balance'
}
],
type: '{"d":"AccountId","e":"Balance"}'
}
],
type: '{"a":"u32","b":"(u32,bool)","c":{"d":"AccountId","e":"Balance"}}'
});
});
it('creates a Vec with nested fixed', (): void => {
expect(
getTypeDef('Vec<[[[bool; 3]; 6]; 9]>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 9,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 6,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.VecFixed,
length: 3,
name: undefined,
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
},
type: '[bool;3]'
},
type: '[[bool;3];6]'
},
type: '[[[bool;3];6];9]'
},
type: 'Vec<[[[bool;3];6];9]>'
});
});
it('creates a Vec with nested struct', (): void => {
expect(
getTypeDef('Vec<{ "a": "u32", "b": "(u32, bool)" }>')
).toEqual({
displayName: undefined,
info: TypeDefInfo.Vec,
name: undefined,
sub: {
alias: undefined,
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Struct,
name: undefined,
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: 'a',
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Tuple,
name: 'b',
sub: [
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'u32'
},
{
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'bool'
}
],
type: '(u32,bool)'
}
],
type: '{"a":"u32","b":"(u32,bool)"}'
},
type: 'Vec<{"a":"u32","b":"(u32,bool)"}>'
});
});
it('creates recursive structures', (): void => {
const registry = new TypeRegistry();
registry.register({
Recursive: {
data: 'Vec<Recursive>'
}
});
const raw = registry.createType('Recursive').toRawType();
expect(
getTypeDef(raw)
).toEqual({
alias: undefined,
displayName: undefined,
fallbackType: undefined,
info: TypeDefInfo.Struct,
name: undefined,
sub: [{
displayName: undefined,
info: TypeDefInfo.Vec,
name: 'data',
sub: {
displayName: undefined,
info: TypeDefInfo.Plain,
name: undefined,
type: 'Recursive'
},
type: 'Vec<Recursive>'
}],
type: '{"data":"Vec<Recursive>"}'
});
});
});
@@ -0,0 +1,279 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AnyString } from '@pezkuwi/types-codec/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import { sanitize } from '@pezkuwi/types-codec';
import { isNumber, isString, objectSpread, stringify } from '@pezkuwi/util';
import { TypeDefInfo } from '../types/index.js';
import { typeSplit } from './typeSplit.js';
interface TypeDefOptions {
name?: string;
displayName?: string;
}
interface SetDetails {
_bitLength: number;
index: number;
[key: string]: number;
}
interface ParsedDef {
_alias: string;
_enum?: string[];
_fallback?: string;
_set?: SetDetails;
[key: string]: unknown;
}
const KNOWN_INTERNALS = ['_alias', '_fallback'];
function getTypeString (typeOrObj: any): string {
return isString(typeOrObj)
? typeOrObj.toString()
: stringify(typeOrObj);
}
function isRustEnum (details: Record<string, string> | Record<string, number>): details is Record<string, string> {
const values = Object.values(details);
if (values.some((v) => isNumber(v))) {
if (!values.every((v) => isNumber(v) && v >= 0 && v <= 255)) {
throw new Error('Invalid number-indexed enum definition');
}
return false;
}
return true;
}
// decode an enum of either of the following forms
// { _enum: ['A', 'B', 'C'] }
// { _enum: { A: AccountId, B: Balance, C: u32 } }
// { _enum: { A: 1, B: 2 } }
function _decodeEnum (value: TypeDef, details: string[] | Record<string, string> | Record<string, number>, count: number, fallbackType?: string): TypeDef {
value.info = TypeDefInfo.Enum;
value.fallbackType = fallbackType;
// not as pretty, but remain compatible with oo7 for both struct and Array types
if (Array.isArray(details)) {
value.sub = details.map((name, index): TypeDef => ({
index,
info: TypeDefInfo.Plain,
name,
type: 'Null'
}));
} else if (isRustEnum(details)) {
value.sub = Object.entries(details).map(([name, typeOrObj], index): TypeDef =>
objectSpread({}, getTypeDef(getTypeString(typeOrObj || 'Null'), { name }, count), { index })
);
} else {
value.sub = Object.entries(details).map(([name, index]): TypeDef => ({
index,
info: TypeDefInfo.Plain,
name,
type: 'Null'
}));
}
return value;
}
// decode a set of the form
// { _set: { A: 0b0001, B: 0b0010, C: 0b0100 } }
function _decodeSet (value: TypeDef, details: SetDetails, fallbackType: string | undefined): TypeDef {
value.info = TypeDefInfo.Set;
value.fallbackType = fallbackType;
value.length = details._bitLength;
value.sub = Object
.entries(details)
.filter(([name]): boolean => !name.startsWith('_'))
.map(([name, index]): TypeDef => ({
index,
info: TypeDefInfo.Plain,
name,
type: 'Null'
}));
return value;
}
// decode a struct, set or enum
function _decodeStruct (value: TypeDef, type: string, _: string, count: number): TypeDef {
const parsed = JSON.parse(type) as ParsedDef;
const keys = Object.keys(parsed);
if (parsed._enum) {
return _decodeEnum(value, parsed._enum, count, parsed._fallback);
} else if (parsed._set) {
return _decodeSet(value, parsed._set, parsed._fallback);
}
value.alias = parsed._alias
? new Map(Object.entries(parsed._alias))
: undefined;
value.fallbackType = parsed._fallback;
value.sub = keys
.filter((name) => !KNOWN_INTERNALS.includes(name))
.map((name) =>
getTypeDef(getTypeString(parsed[name]), { name }, count)
);
return value;
}
// decode a fixed vector, e.g. [u8;32]
function _decodeFixedVec (value: TypeDef, type: string, _: string, count: number): TypeDef {
const max = type.length - 1;
let index = -1;
let inner = 0;
for (let i = 1; (i < max) && (index === -1); i++) {
switch (type[i]) {
case ';': {
if (inner === 0) {
index = i;
}
break;
}
case '[':
case '(':
case '<':
inner++;
break;
case ']':
case ')':
case '>':
inner--;
break;
}
}
if (index === -1) {
throw new Error(`${type}: Unable to extract location of ';'`);
}
const vecType = type.substring(1, index);
const [strLength, displayName] = type.substring(index + 1, max).split(';');
const length = parseInt(strLength.trim(), 10);
if (length > 2048) {
throw new Error(`${type}: Only support for [Type; <length>], where length <= 2048`);
}
value.displayName = displayName;
value.length = length;
value.sub = getTypeDef(vecType, {}, count);
return value;
}
// decode a tuple
function _decodeTuple (value: TypeDef, _: string, subType: string, count: number): TypeDef {
value.sub = subType.length === 0
? []
: typeSplit(subType).map((inner) => getTypeDef(inner, {}, count));
return value;
}
// decode a Int/UInt<bitLength[, name]>
function _decodeAnyInt (value: TypeDef, type: string, _: string, clazz: 'Int' | 'UInt'): TypeDef {
const [strLength, displayName] = type.substring(clazz.length + 1, type.length - 1).split(',');
const length = parseInt(strLength.trim(), 10);
if ((length > 8192) || (length % 8)) {
throw new Error(`${type}: Only support for ${clazz}<bitLength>, where length <= 8192 and a power of 8, found ${length}`);
}
value.displayName = displayName;
value.length = length;
return value;
}
function _decodeInt (value: TypeDef, type: string, subType: string): TypeDef {
return _decodeAnyInt(value, type, subType, 'Int');
}
function _decodeUInt (value: TypeDef, type: string, subType: string): TypeDef {
return _decodeAnyInt(value, type, subType, 'UInt');
}
function _decodeDoNotConstruct (value: TypeDef, type: string, _: string): TypeDef {
const NAME_LENGTH = 'DoNotConstruct'.length;
value.displayName = type.substring(NAME_LENGTH + 1, type.length - 1);
return value;
}
function hasWrapper (type: string, [start, end]: [string, string, TypeDefInfo, any?]): boolean {
return (type.startsWith(start)) && (type.slice(-1 * end.length) === end);
}
const nestedExtraction: [string, string, TypeDefInfo, (value: TypeDef, type: string, subType: string, count: number) => TypeDef][] = [
['[', ']', TypeDefInfo.VecFixed, _decodeFixedVec],
['{', '}', TypeDefInfo.Struct, _decodeStruct],
['(', ')', TypeDefInfo.Tuple, _decodeTuple],
// the inner for these are the same as tuple, multiple values
['BTreeMap<', '>', TypeDefInfo.BTreeMap, _decodeTuple],
['HashMap<', '>', TypeDefInfo.HashMap, _decodeTuple],
['Int<', '>', TypeDefInfo.Int, _decodeInt],
['Result<', '>', TypeDefInfo.Result, _decodeTuple],
['UInt<', '>', TypeDefInfo.UInt, _decodeUInt],
['DoNotConstruct<', '>', TypeDefInfo.DoNotConstruct, _decodeDoNotConstruct]
];
const wrappedExtraction: [string, string, TypeDefInfo][] = [
['BTreeSet<', '>', TypeDefInfo.BTreeSet],
['Compact<', '>', TypeDefInfo.Compact],
['Linkage<', '>', TypeDefInfo.Linkage],
['Opaque<', '>', TypeDefInfo.WrapperOpaque],
['Option<', '>', TypeDefInfo.Option],
['Range<', '>', TypeDefInfo.Range],
['RangeInclusive<', '>', TypeDefInfo.RangeInclusive],
['Vec<', '>', TypeDefInfo.Vec],
['WrapperKeepOpaque<', '>', TypeDefInfo.WrapperKeepOpaque],
['WrapperOpaque<', '>', TypeDefInfo.WrapperOpaque]
];
function extractSubType (type: string, [start, end]: [string, string, TypeDefInfo, any?]): string {
return type.substring(start.length, type.length - end.length);
}
export function getTypeDef (_type: AnyString, { displayName, name }: TypeDefOptions = {}, count = 0): TypeDef {
// create the type via Type, allowing types to be sanitized
const type = sanitize(_type);
const value: TypeDef = { displayName, info: TypeDefInfo.Plain, name, type };
if (++count > 64) {
throw new Error('getTypeDef: Maximum nested limit reached');
}
const nested = nestedExtraction.find((nested) => hasWrapper(type, nested));
if (nested) {
value.info = nested[2];
return nested[3](value, type, extractSubType(type, nested), count);
}
const wrapped = wrappedExtraction.find((wrapped) => hasWrapper(type, wrapped));
if (wrapped) {
value.info = wrapped[2];
value.sub = getTypeDef(extractSubType(type, wrapped), {}, count);
}
return value;
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './encodeTypes.js';
export * from './getTypeDef.js';
export * from './typeSplit.js';
export * from './xcm.js';
@@ -0,0 +1,50 @@
// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
import { typeSplit } from '@pezkuwi/types-create';
describe('typeSplit', (): void => {
it('splits simple types into an array', (): void => {
expect(
typeSplit('Text, u32, u64')
).toEqual(['Text', 'u32', 'u64']);
});
it('splits nested combinations', (): void => {
expect(
typeSplit('Text, (u32), Vec<u64>')
).toEqual(['Text', '(u32)', 'Vec<u64>']);
});
it('keeps nested tuples together', (): void => {
expect(
typeSplit('Text, (u32, u128), Vec<u64>')
).toEqual(['Text', '(u32, u128)', 'Vec<u64>']);
});
it('keeps nested vector tuples together', (): void => {
expect(
typeSplit('Text, (u32, u128), Vec<(u64, u32)>')
).toEqual(['Text', '(u32, u128)', 'Vec<(u64, u32)>']);
});
it('allows for deep nesting', (): void => {
expect(
typeSplit('Text, (u32, (u128, u8)), Vec<(u64, (u32, u32))>')
).toEqual(['Text', '(u32, (u128, u8))', 'Vec<(u64, (u32, u32))>']);
});
it('checks for unclosed vec', (): void => {
expect(
() => typeSplit('Text, Vec<u64')
).toThrow(/Invalid definition/);
});
it('checks for unclosed tuple', (): void => {
expect(
() => typeSplit('Text, (u64, u32')
).toThrow(/Invalid definition/);
});
});
@@ -0,0 +1,56 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
// safely split a string on ', ' while taking care of any nested occurences
export function typeSplit (type: string): string[] {
const result: string[] = [];
// these are the depths of the various tokens: <, [, {, (
let c = 0;
let f = 0;
let s = 0;
let t = 0;
// current start position
let start = 0;
for (let i = 0, count = type.length; i < count; i++) {
switch (type[i]) {
// if we are not nested, add the type
case ',': {
if (!(c || f || s || t)) {
result.push(type.substring(start, i).trim());
start = i + 1;
}
break;
}
// adjust compact/vec (and friends) depth
case '<': c++; break;
case '>': c--; break;
// adjust fixed vec depths
case '[': f++; break;
case ']': f--; break;
// adjust struct depth
case '{': s++; break;
case '}': s--; break;
// adjust tuple depth
case '(': t++; break;
case ')': t--; break;
}
}
// ensure we have all the terminators taken care of
if (c || f || s || t) {
throw new Error(`Invalid definition (missing terminators) found in ${type}`);
}
// the final leg of the journey
result.push(type.substring(start, type.length).trim());
return result;
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @pezkuwi/types-create authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { objectSpread } from '@pezkuwi/util';
export const XCM_MAPPINGS = ['AssetInstance', 'Fungibility', 'Junction', 'Junctions', 'MultiAsset', 'MultiAssetFilter', 'MultiLocation', 'Response', 'WildFungibility', 'WildMultiAsset', 'Xcm', 'XcmError'];
export function mapXcmTypes (version: 'V0' | 'V1' | 'V2' | 'V3' | 'V4' | 'V5'): Record<string, string> {
return XCM_MAPPINGS.reduce<Record<string, string>>((all, key) =>
objectSpread(all, { [key]: `${key}${version}` }), {}
);
}