mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-04-22 04:27:56 +00:00
Rebrand: polkadot → pezkuwi, substrate → bizinikiwi, kusama → dicle
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# @pezkuwi/types-create
|
||||
|
||||
Base type-creation functionality.
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-api/issues",
|
||||
"description": "Type creator helpers",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-api/tree/master/packages/types-create#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/types-create",
|
||||
"repository": {
|
||||
"directory": "packages/types-create",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-api.git"
|
||||
},
|
||||
"sideEffects": [
|
||||
"./packageDetect.js",
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "16.5.6",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@pezkuwi/types-codec": "16.5.4",
|
||||
"@pezkuwi/util": "^14.0.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/types": "16.5.4"
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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]);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}` }), {}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/mod.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../types-codec/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../types/tsconfig.build.json" },
|
||||
{ "path": "../types-codec/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user