diff --git a/.changeset/grumpy-knives-provide.md b/.changeset/grumpy-knives-provide.md new file mode 100644 index 000000000..2a4fcd55a --- /dev/null +++ b/.changeset/grumpy-knives-provide.md @@ -0,0 +1,5 @@ +--- +"@metaplex-foundation/kinobi": minor +--- + +Make nodes generic interfaces diff --git a/src/nodes/AccountNode.ts b/src/nodes/AccountNode.ts index 8f844f9c4..7b926f780 100644 --- a/src/nodes/AccountNode.ts +++ b/src/nodes/AccountNode.ts @@ -3,29 +3,56 @@ import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../shared'; import { assertIsNode } from './Node'; import { DiscriminatorNode } from './discriminatorNodes'; import { PdaLinkNode, pdaLinkNode } from './linkNodes'; -import { StructTypeNode, structTypeNode } from './typeNodes'; +import { + ResolveNestedTypeNode, + StructTypeNode, + structTypeNode, +} from './typeNodes'; import { createTypeNodeFromIdl } from './typeNodes/TypeNode'; -export type AccountNode = { +export interface AccountNode< + TData extends + ResolveNestedTypeNode = ResolveNestedTypeNode, + TPda extends PdaLinkNode | undefined = PdaLinkNode | undefined, + TDiscriminators extends DiscriminatorNode[] | undefined = + | DiscriminatorNode[] + | undefined, +> { readonly kind: 'accountNode'; // Children. - readonly data: StructTypeNode; - readonly pda?: PdaLinkNode; - readonly discriminators?: DiscriminatorNode[]; + readonly data: TData; + readonly pda?: TPda; + readonly discriminators?: TDiscriminators; // Data. readonly name: MainCaseString; readonly idlName: string; readonly docs: string[]; readonly size?: number | null; -}; +} -export type AccountNodeInput = Omit, 'kind' | 'name'> & { +export type AccountNodeInput< + TData extends + ResolveNestedTypeNode = ResolveNestedTypeNode, + TPda extends PdaLinkNode | undefined = PdaLinkNode | undefined, + TDiscriminators extends DiscriminatorNode[] | undefined = + | DiscriminatorNode[] + | undefined, +> = Omit< + Partial>, + 'kind' | 'name' +> & { readonly name: string; }; -export function accountNode(input: AccountNodeInput): AccountNode { +export function accountNode< + TData extends ResolveNestedTypeNode = StructTypeNode<[]>, + TPda extends PdaLinkNode | undefined = undefined, + const TDiscriminators extends DiscriminatorNode[] | undefined = undefined, +>( + input: AccountNodeInput +): AccountNode { if (!input.name) { throw new InvalidKinobiTreeError('AccountNode must have a name.'); } @@ -33,7 +60,7 @@ export function accountNode(input: AccountNodeInput): AccountNode { kind: 'accountNode', // Children. - data: input.data ?? structTypeNode([]), + data: (input.data ?? structTypeNode([])) as TData, pda: input.pda, discriminators: input.discriminators, @@ -45,7 +72,9 @@ export function accountNode(input: AccountNodeInput): AccountNode { }; } -export function accountNodeFromIdl(idl: Partial): AccountNode { +export function accountNodeFromIdl( + idl: Partial +): AccountNode { const idlName = idl.name ?? ''; const name = mainCase(idlName); const idlStruct = idl.type ?? { kind: 'struct', fields: [] }; diff --git a/src/nodes/DefinedTypeNode.ts b/src/nodes/DefinedTypeNode.ts index 8ce97bcfc..914ce7610 100644 --- a/src/nodes/DefinedTypeNode.ts +++ b/src/nodes/DefinedTypeNode.ts @@ -2,26 +2,28 @@ import type { IdlDefinedType } from '../idl'; import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../shared'; import { TypeNode, createTypeNodeFromIdl } from './typeNodes/TypeNode'; -export type DefinedTypeNode = { +export interface DefinedTypeNode { readonly kind: 'definedTypeNode'; // Children. - readonly type: TypeNode; + readonly type: TType; // Data. readonly name: MainCaseString; readonly idlName: string; readonly docs: string[]; -}; +} -export type DefinedTypeNodeInput = { +export type DefinedTypeNodeInput = { readonly name: string; - readonly type: TypeNode; + readonly type: TType; readonly idlName?: string; readonly docs?: string[]; }; -export function definedTypeNode(input: DefinedTypeNodeInput): DefinedTypeNode { +export function definedTypeNode( + input: DefinedTypeNodeInput +): DefinedTypeNode { if (!input.name) { throw new InvalidKinobiTreeError('DefinedTypeNode must have a name.'); } diff --git a/src/nodes/ErrorNode.ts b/src/nodes/ErrorNode.ts index b80e651d5..6efa12f26 100644 --- a/src/nodes/ErrorNode.ts +++ b/src/nodes/ErrorNode.ts @@ -6,7 +6,7 @@ import { mainCase, } from '../shared'; -export type ErrorNode = { +export interface ErrorNode { readonly kind: 'errorNode'; // Data. @@ -15,7 +15,7 @@ export type ErrorNode = { readonly code: number; readonly message: string; readonly docs: string[]; -}; +} export type ErrorNodeInput = Omit< PartialExcept, diff --git a/src/nodes/InstructionAccountNode.ts b/src/nodes/InstructionAccountNode.ts index 5d3a3375c..68d2b5bda 100644 --- a/src/nodes/InstructionAccountNode.ts +++ b/src/nodes/InstructionAccountNode.ts @@ -2,11 +2,15 @@ import { IdlInstructionAccount } from '../idl'; import { MainCaseString, PartialExcept, mainCase } from '../shared'; import { InstructionInputValueNode } from './contextualValueNodes'; -export type InstructionAccountNode = { +export interface InstructionAccountNode< + TDefaultValue extends InstructionInputValueNode | undefined = + | InstructionInputValueNode + | undefined, +> { readonly kind: 'instructionAccountNode'; // Children. - readonly defaultValue?: InstructionInputValueNode; + readonly defaultValue?: TDefaultValue; // Data. readonly name: MainCaseString; @@ -14,18 +18,27 @@ export type InstructionAccountNode = { readonly isSigner: boolean | 'either'; readonly isOptional: boolean; readonly docs: string[]; -}; +} -export type InstructionAccountNodeInput = Omit< - PartialExcept, +export type InstructionAccountNodeInput< + TDefaultValue extends InstructionInputValueNode | undefined = + | InstructionInputValueNode + | undefined, +> = Omit< + PartialExcept< + InstructionAccountNode, + 'isWritable' | 'isSigner' + >, 'kind' | 'name' > & { readonly name: string; }; -export function instructionAccountNode( - input: InstructionAccountNodeInput -): InstructionAccountNode { +export function instructionAccountNode< + TDefaultValue extends InstructionInputValueNode | undefined = undefined, +>( + input: InstructionAccountNodeInput +): InstructionAccountNode { return { kind: 'instructionAccountNode', name: mainCase(input.name), diff --git a/src/nodes/InstructionArgumentNode.ts b/src/nodes/InstructionArgumentNode.ts index 8797ef7c1..067725920 100644 --- a/src/nodes/InstructionArgumentNode.ts +++ b/src/nodes/InstructionArgumentNode.ts @@ -10,30 +10,40 @@ import { } from './typeNodes'; import { VALUE_NODES } from './valueNodes'; -export type InstructionArgumentNode = { +export interface InstructionArgumentNode< + TDefaultValue extends InstructionInputValueNode | undefined = + | InstructionInputValueNode + | undefined, +> { readonly kind: 'instructionArgumentNode'; // Children. readonly type: TypeNode; - readonly defaultValue?: InstructionInputValueNode; + readonly defaultValue?: TDefaultValue; // Data. readonly name: MainCaseString; readonly docs: string[]; readonly defaultValueStrategy?: 'optional' | 'omitted'; -}; +} -export type InstructionArgumentNodeInput = { +export type InstructionArgumentNodeInput< + TDefaultValue extends InstructionInputValueNode | undefined = + | InstructionInputValueNode + | undefined, +> = { readonly name: string; readonly type: TypeNode; readonly docs?: string[]; - readonly defaultValue?: InstructionInputValueNode; + readonly defaultValue?: TDefaultValue; readonly defaultValueStrategy?: 'optional' | 'omitted'; }; -export function instructionArgumentNode( - input: InstructionArgumentNodeInput -): InstructionArgumentNode { +export function instructionArgumentNode< + TDefaultValue extends InstructionInputValueNode | undefined = undefined, +>( + input: InstructionArgumentNodeInput +): InstructionArgumentNode { if (!input.name) { throw new InvalidKinobiTreeError( 'InstructionArgumentNode must have a name.' diff --git a/src/nodes/InstructionByteDeltaNode.ts b/src/nodes/InstructionByteDeltaNode.ts index 41bd82be0..3922632e1 100644 --- a/src/nodes/InstructionByteDeltaNode.ts +++ b/src/nodes/InstructionByteDeltaNode.ts @@ -3,28 +3,34 @@ import { ArgumentValueNode, ResolverValueNode } from './contextualValueNodes'; import { AccountLinkNode } from './linkNodes'; import { NumberValueNode } from './valueNodes'; -export type InstructionByteDeltaNode = { +type InstructionByteDeltaNodeValue = + | NumberValueNode + | AccountLinkNode + | ArgumentValueNode + | ResolverValueNode; + +export interface InstructionByteDeltaNode< + TValue extends InstructionByteDeltaNodeValue = InstructionByteDeltaNodeValue, +> { readonly kind: 'instructionByteDeltaNode'; // Children. - readonly value: - | NumberValueNode - | AccountLinkNode - | ArgumentValueNode - | ResolverValueNode; + readonly value: TValue; // Data. readonly withHeader: boolean; readonly subtract?: boolean; -}; +} -export function instructionByteDeltaNode( - value: InstructionByteDeltaNode['value'], +export function instructionByteDeltaNode< + TValue extends InstructionByteDeltaNodeValue, +>( + value: TValue, options: { withHeader?: boolean; subtract?: boolean; } = {} -): InstructionByteDeltaNode { +): InstructionByteDeltaNode { return { kind: 'instructionByteDeltaNode', value, diff --git a/src/nodes/InstructionNode.ts b/src/nodes/InstructionNode.ts index e9b5aac1b..114278784 100644 --- a/src/nodes/InstructionNode.ts +++ b/src/nodes/InstructionNode.ts @@ -21,33 +21,111 @@ import { import { createTypeNodeFromIdl } from './typeNodes/TypeNode'; import { numberValueNode } from './valueNodes'; -export type InstructionNode = { +type SubInstructionNode = InstructionNode; + +export interface InstructionNode< + TAccounts extends InstructionAccountNode[] = InstructionAccountNode[], + TArguments extends InstructionArgumentNode[] = InstructionArgumentNode[], + TExtraArguments extends InstructionArgumentNode[] | undefined = + | InstructionArgumentNode[] + | undefined, + TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined = + | InstructionRemainingAccountsNode[] + | undefined, + TByteDeltas extends InstructionByteDeltaNode[] | undefined = + | InstructionByteDeltaNode[] + | undefined, + TDiscriminators extends DiscriminatorNode[] | undefined = + | DiscriminatorNode[] + | undefined, + TSubInstructions extends SubInstructionNode[] | undefined = + | SubInstructionNode[] + | undefined, +> { readonly kind: 'instructionNode'; // Children. - readonly accounts: InstructionAccountNode[]; - readonly arguments: InstructionArgumentNode[]; - readonly extraArguments?: InstructionArgumentNode[]; - readonly remainingAccounts?: InstructionRemainingAccountsNode[]; - readonly byteDeltas?: InstructionByteDeltaNode[]; - readonly discriminators?: DiscriminatorNode[]; - readonly subInstructions?: InstructionNode[]; + readonly accounts: TAccounts; + readonly arguments: TArguments; + readonly extraArguments?: TExtraArguments; + readonly remainingAccounts?: TRemainingAccounts; + readonly byteDeltas?: TByteDeltas; + readonly discriminators?: TDiscriminators; + readonly subInstructions?: TSubInstructions; // Data. readonly name: MainCaseString; readonly idlName: string; readonly docs: string[]; readonly optionalAccountStrategy: 'omitted' | 'programId'; -}; +} -export type InstructionNodeInput = Omit< - Partial, +export type InstructionNodeInput< + TAccounts extends InstructionAccountNode[] = InstructionAccountNode[], + TArguments extends InstructionArgumentNode[] = InstructionArgumentNode[], + TExtraArguments extends InstructionArgumentNode[] | undefined = + | InstructionArgumentNode[] + | undefined, + TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined = + | InstructionRemainingAccountsNode[] + | undefined, + TByteDeltas extends InstructionByteDeltaNode[] | undefined = + | InstructionByteDeltaNode[] + | undefined, + TDiscriminators extends DiscriminatorNode[] | undefined = + | DiscriminatorNode[] + | undefined, + TSubInstructions extends SubInstructionNode[] | undefined = + | SubInstructionNode[] + | undefined, +> = Omit< + Partial< + InstructionNode< + TAccounts, + TArguments, + TExtraArguments, + TRemainingAccounts, + TByteDeltas, + TDiscriminators, + TSubInstructions + > + >, 'kind' | 'name' > & { readonly name: string; }; -export function instructionNode(input: InstructionNodeInput): InstructionNode { +export function instructionNode< + const TAccounts extends InstructionAccountNode[] = [], + const TArguments extends InstructionArgumentNode[] = [], + const TExtraArguments extends + | InstructionArgumentNode[] + | undefined = undefined, + const TRemainingAccounts extends + | InstructionRemainingAccountsNode[] + | undefined = undefined, + const TByteDeltas extends InstructionByteDeltaNode[] | undefined = undefined, + const TDiscriminators extends DiscriminatorNode[] | undefined = undefined, + const TSubInstructions extends SubInstructionNode[] | undefined = undefined, +>( + input: InstructionNodeInput< + TAccounts, + TArguments, + TExtraArguments, + TRemainingAccounts, + TByteDeltas, + TDiscriminators, + TSubInstructions + > +): InstructionNode< + TAccounts, + TArguments, + TExtraArguments, + TRemainingAccounts, + TByteDeltas, + TDiscriminators, + TSubInstructions +> { if (!input.name) { throw new InvalidKinobiTreeError('InstructionNode must have a name.'); } @@ -56,8 +134,8 @@ export function instructionNode(input: InstructionNodeInput): InstructionNode { kind: 'instructionNode', // Children. - accounts: input.accounts ?? [], - arguments: input.arguments ?? [], + accounts: (input.accounts ?? []) as TAccounts, + arguments: (input.arguments ?? []) as TArguments, extraArguments: input.extraArguments, remainingAccounts: input.remainingAccounts, byteDeltas: input.byteDeltas, diff --git a/src/nodes/InstructionRemainingAccountsNode.ts b/src/nodes/InstructionRemainingAccountsNode.ts index 517a8c258..9961e83a6 100644 --- a/src/nodes/InstructionRemainingAccountsNode.ts +++ b/src/nodes/InstructionRemainingAccountsNode.ts @@ -1,30 +1,31 @@ import { ArgumentValueNode, ResolverValueNode } from './contextualValueNodes'; -export type InstructionRemainingAccountsNode = { +export interface InstructionRemainingAccountsNode< + TValue extends ArgumentValueNode | ResolverValueNode = + | ArgumentValueNode + | ResolverValueNode, +> { readonly kind: 'instructionRemainingAccountsNode'; // Children. - readonly value: ArgumentValueNode | ResolverValueNode; + readonly value: TValue; // Data. readonly isOptional?: boolean; readonly isSigner?: boolean | 'either'; readonly isWritable?: boolean; -}; - -export type InstructionRemainingAccountsNodeInput = Omit< - InstructionRemainingAccountsNode, - 'kind' ->; +} -export function instructionRemainingAccountsNode( - value: ArgumentValueNode | ResolverValueNode, +export function instructionRemainingAccountsNode< + TValue extends ArgumentValueNode | ResolverValueNode, +>( + value: TValue, options: { isOptional?: boolean; isSigner?: boolean | 'either'; isWritable?: boolean; } = {} -): InstructionRemainingAccountsNode { +): InstructionRemainingAccountsNode { return { kind: 'instructionRemainingAccountsNode', value, diff --git a/src/nodes/Node.ts b/src/nodes/Node.ts index 296e94d76..2ed635045 100644 --- a/src/nodes/Node.ts +++ b/src/nodes/Node.ts @@ -1,4 +1,3 @@ -import { getNodeKinds } from '../shared/utils'; import type { AccountNode } from './AccountNode'; import type { DefinedTypeNode } from './DefinedTypeNode'; import type { ErrorNode } from './ErrorNode'; @@ -10,50 +9,91 @@ import type { InstructionRemainingAccountsNode } from './InstructionRemainingAcc import type { PdaNode } from './PdaNode'; import type { ProgramNode } from './ProgramNode'; import type { RootNode } from './RootNode'; -import { REGISTERED_CONTEXTUAL_VALUE_NODES } from './contextualValueNodes/ContextualValueNode'; -import { REGISTERED_DISCRIMINATOR_NODES } from './discriminatorNodes/DiscriminatorNode'; -import { REGISTERED_LINK_NODES } from './linkNodes/LinkNode'; -import { REGISTERED_PDA_SEED_NODES } from './pdaSeedNodes/PdaSeedNode'; -import { REGISTERED_COUNT_NODES } from './countNodes/CountNode'; -import { REGISTERED_TYPE_NODES } from './typeNodes/TypeNode'; -import { REGISTERED_VALUE_NODES } from './valueNodes/ValueNode'; +import { + REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS, + RegisteredContextualValueNode, +} from './contextualValueNodes/ContextualValueNode'; +import { + REGISTERED_COUNT_NODE_KINDS, + RegisteredCountNode, +} from './countNodes/CountNode'; +import { + REGISTERED_DISCRIMINATOR_NODE_KINDS, + RegisteredDiscriminatorNode, +} from './discriminatorNodes/DiscriminatorNode'; +import { + REGISTERED_LINK_NODE_KINDS, + RegisteredLinkNode, +} from './linkNodes/LinkNode'; +import { + REGISTERED_PDA_SEED_NODE_KINDS, + RegisteredPdaSeedNode, +} from './pdaSeedNodes/PdaSeedNode'; +import { + REGISTERED_TYPE_NODE_KINDS, + RegisteredTypeNode, +} from './typeNodes/TypeNode'; +import { + REGISTERED_VALUE_NODE_KINDS, + RegisteredValueNode, +} from './valueNodes/ValueNode'; // Node Registration. - -const REGISTERED_NODES = { - rootNode: {} as RootNode, - programNode: {} as ProgramNode, - pdaNode: {} as PdaNode, - accountNode: {} as AccountNode, - instructionAccountNode: {} as InstructionAccountNode, - instructionArgumentNode: {} as InstructionArgumentNode, - instructionByteDeltaNode: {} as InstructionByteDeltaNode, - instructionNode: {} as InstructionNode, - instructionRemainingAccountsNode: {} as InstructionRemainingAccountsNode, - errorNode: {} as ErrorNode, - definedTypeNode: {} as DefinedTypeNode, - +export type Node = // Groups. - ...REGISTERED_CONTEXTUAL_VALUE_NODES, - ...REGISTERED_DISCRIMINATOR_NODES, - ...REGISTERED_LINK_NODES, - ...REGISTERED_PDA_SEED_NODES, - ...REGISTERED_COUNT_NODES, - ...REGISTERED_TYPE_NODES, - ...REGISTERED_VALUE_NODES, -}; - -export const REGISTERED_NODE_KINDS = getNodeKinds(REGISTERED_NODES); -export type NodeDictionary = typeof REGISTERED_NODES; -export type NodeKind = keyof NodeDictionary; -export type Node = NodeDictionary[NodeKind]; + | RegisteredContextualValueNode + | RegisteredCountNode + | RegisteredDiscriminatorNode + | RegisteredLinkNode + | RegisteredPdaSeedNode + | RegisteredTypeNode + | RegisteredValueNode + // Nodes. + | RootNode + | ProgramNode + | PdaNode + | AccountNode + | InstructionAccountNode + | InstructionArgumentNode + | InstructionByteDeltaNode + | InstructionNode + | InstructionRemainingAccountsNode + | ErrorNode + | DefinedTypeNode; +export const REGISTERED_NODE_KINDS = [ + ...REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS, + ...REGISTERED_DISCRIMINATOR_NODE_KINDS, + ...REGISTERED_LINK_NODE_KINDS, + ...REGISTERED_PDA_SEED_NODE_KINDS, + ...REGISTERED_COUNT_NODE_KINDS, + ...REGISTERED_TYPE_NODE_KINDS, + ...REGISTERED_VALUE_NODE_KINDS, + 'rootNode', + 'programNode', + 'pdaNode', + 'accountNode', + 'instructionAccountNode', + 'instructionArgumentNode', + 'instructionByteDeltaNode', + 'instructionNode', + 'instructionRemainingAccountsNode', + 'errorNode', + 'definedTypeNode', +] satisfies readonly Node['kind'][]; +null as unknown as Node['kind'] satisfies (typeof REGISTERED_NODE_KINDS)[number]; +export type NodeKind = Node['kind']; // Node Helpers. +export type GetNodeFromKind = Extract< + Node, + { kind: TKind } +>; + export function isNode( node: Node | null | undefined, kind: TKind | TKind[] -): node is NodeDictionary[TKind] { +): node is GetNodeFromKind { const kinds = Array.isArray(kind) ? kind : [kind]; return !!node && (kinds as NodeKind[]).includes(node.kind); } @@ -61,7 +101,7 @@ export function isNode( export function assertIsNode( node: Node | null | undefined, kind: TKind | TKind[] -): asserts node is NodeDictionary[TKind] { +): asserts node is GetNodeFromKind { const kinds = Array.isArray(kind) ? kind : [kind]; if (!isNode(node, kinds)) { throw new Error( @@ -72,14 +112,14 @@ export function assertIsNode( export function isNodeFilter( kind: TKind | TKind[] -): (node: Node | null | undefined) => node is NodeDictionary[TKind] { - return (node): node is NodeDictionary[TKind] => isNode(node, kind); +): (node: Node | null | undefined) => node is GetNodeFromKind { + return (node): node is GetNodeFromKind => isNode(node, kind); } export function assertIsNodeFilter( kind: TKind | TKind[] -): (node: Node | null | undefined) => node is NodeDictionary[TKind] { - return (node): node is NodeDictionary[TKind] => { +): (node: Node | null | undefined) => node is GetNodeFromKind { + return (node): node is GetNodeFromKind => { assertIsNode(node, kind); return true; }; @@ -87,8 +127,8 @@ export function assertIsNodeFilter( export function removeNullAndAssertIsNodeFilter( kind: TKind | TKind[] -): (node: Node | null | undefined) => node is NodeDictionary[TKind] { - return (node): node is NodeDictionary[TKind] => { +): (node: Node | null | undefined) => node is GetNodeFromKind { + return (node): node is GetNodeFromKind => { if (node) assertIsNode(node, kind); return node != null; }; diff --git a/src/nodes/PdaNode.ts b/src/nodes/PdaNode.ts index 43883d316..9b4a9022c 100644 --- a/src/nodes/PdaNode.ts +++ b/src/nodes/PdaNode.ts @@ -17,17 +17,20 @@ import { stringValueNode, } from './valueNodes'; -export type PdaNode = { +export interface PdaNode { readonly kind: 'pdaNode'; // Children. - readonly seeds: PdaSeedNode[]; + readonly seeds: TSeeds; // Data. readonly name: MainCaseString; -}; +} -export function pdaNode(name: string, seeds: PdaSeedNode[]): PdaNode { +export function pdaNode( + name: string, + seeds: TSeeds +): PdaNode { if (!name) { throw new InvalidKinobiTreeError('PdaNode must have a name.'); } diff --git a/src/nodes/ProgramNode.ts b/src/nodes/ProgramNode.ts index 104566acb..48df96ec9 100644 --- a/src/nodes/ProgramNode.ts +++ b/src/nodes/ProgramNode.ts @@ -6,15 +6,21 @@ import { ErrorNode, errorNodeFromIdl } from './ErrorNode'; import { InstructionNode, instructionNodeFromIdl } from './InstructionNode'; import { PdaNode, pdaNodeFromIdl } from './PdaNode'; -export type ProgramNode = { +export interface ProgramNode< + TPdas extends PdaNode[] = PdaNode[], + TAccounts extends AccountNode[] = AccountNode[], + TInstructions extends InstructionNode[] = InstructionNode[], + TDefinedTypes extends DefinedTypeNode[] = DefinedTypeNode[], + TErrors extends ErrorNode[] = ErrorNode[], +> { readonly kind: 'programNode'; // Children. - readonly pdas: PdaNode[]; - readonly accounts: AccountNode[]; - readonly instructions: InstructionNode[]; - readonly definedTypes: DefinedTypeNode[]; - readonly errors: ErrorNode[]; + readonly pdas: TPdas; + readonly accounts: TAccounts; + readonly instructions: TInstructions; + readonly definedTypes: TDefinedTypes; + readonly errors: TErrors; // Data. readonly name: MainCaseString; @@ -22,24 +28,47 @@ export type ProgramNode = { readonly publicKey: string; readonly version: string; readonly origin?: 'shank' | 'anchor'; -}; +} -export type ProgramNodeInput = Omit< - PartialExcept, +export type ProgramNodeInput< + TPdas extends PdaNode[] = PdaNode[], + TAccounts extends AccountNode[] = AccountNode[], + TInstructions extends InstructionNode[] = InstructionNode[], + TDefinedTypes extends DefinedTypeNode[] = DefinedTypeNode[], + TErrors extends ErrorNode[] = ErrorNode[], +> = Omit< + PartialExcept< + ProgramNode, + 'publicKey' + >, 'kind' | 'name' | 'prefix' > & { readonly name: string; readonly prefix?: string; }; -export function programNode(input: ProgramNodeInput): ProgramNode { +export function programNode< + const TPdas extends PdaNode[] = [], + const TAccounts extends AccountNode[] = [], + const TInstructions extends InstructionNode[] = [], + const TDefinedTypes extends DefinedTypeNode[] = [], + const TErrors extends ErrorNode[] = [], +>( + input: ProgramNodeInput< + TPdas, + TAccounts, + TInstructions, + TDefinedTypes, + TErrors + > +): ProgramNode { return { kind: 'programNode', - pdas: input.pdas ?? [], - accounts: input.accounts ?? [], - instructions: input.instructions ?? [], - definedTypes: input.definedTypes ?? [], - errors: input.errors ?? [], + pdas: (input.pdas ?? []) as TPdas, + accounts: (input.accounts ?? []) as TAccounts, + instructions: (input.instructions ?? []) as TInstructions, + definedTypes: (input.definedTypes ?? []) as TDefinedTypes, + errors: (input.errors ?? []) as TErrors, name: mainCase(input.name), prefix: mainCase(input.prefix ?? ''), publicKey: input.publicKey, diff --git a/src/nodes/RootNode.ts b/src/nodes/RootNode.ts index 808dcaf85..cc09b7ee0 100644 --- a/src/nodes/RootNode.ts +++ b/src/nodes/RootNode.ts @@ -9,14 +9,16 @@ import { ProgramNode, programNodeFromIdl } from './ProgramNode'; export type IdlInputs = string | Partial | (string | Partial)[]; -export type RootNode = { +export interface RootNode { readonly kind: 'rootNode'; // Children. - readonly programs: ProgramNode[]; -}; + readonly programs: TPrograms; +} -export function rootNode(programs: ProgramNode[]): RootNode { +export function rootNode( + programs: TPrograms +): RootNode { return { kind: 'rootNode', programs }; } diff --git a/src/nodes/contextualValueNodes/AccountBumpValueNode.ts b/src/nodes/contextualValueNodes/AccountBumpValueNode.ts index 95e06cc4a..e77b2c857 100644 --- a/src/nodes/contextualValueNodes/AccountBumpValueNode.ts +++ b/src/nodes/contextualValueNodes/AccountBumpValueNode.ts @@ -1,11 +1,11 @@ import { MainCaseString, mainCase } from '../../shared'; -export type AccountBumpValueNode = { +export interface AccountBumpValueNode { readonly kind: 'accountBumpValueNode'; // Data. readonly name: MainCaseString; -}; +} export function accountBumpValueNode(name: string): AccountBumpValueNode { return { kind: 'accountBumpValueNode', name: mainCase(name) }; diff --git a/src/nodes/contextualValueNodes/AccountValueNode.ts b/src/nodes/contextualValueNodes/AccountValueNode.ts index 9e687a1e6..1c8f22e17 100644 --- a/src/nodes/contextualValueNodes/AccountValueNode.ts +++ b/src/nodes/contextualValueNodes/AccountValueNode.ts @@ -1,11 +1,11 @@ import { MainCaseString, mainCase } from '../../shared'; -export type AccountValueNode = { +export interface AccountValueNode { readonly kind: 'accountValueNode'; // Data. readonly name: MainCaseString; -}; +} export function accountValueNode(name: string): AccountValueNode { return { kind: 'accountValueNode', name: mainCase(name) }; diff --git a/src/nodes/contextualValueNodes/ArgumentValueNode.ts b/src/nodes/contextualValueNodes/ArgumentValueNode.ts index 47ed83c38..c932b76a6 100644 --- a/src/nodes/contextualValueNodes/ArgumentValueNode.ts +++ b/src/nodes/contextualValueNodes/ArgumentValueNode.ts @@ -1,11 +1,11 @@ import { MainCaseString, mainCase } from '../../shared'; -export type ArgumentValueNode = { +export interface ArgumentValueNode { readonly kind: 'argumentValueNode'; // Data. readonly name: MainCaseString; -}; +} export function argumentValueNode(name: string): ArgumentValueNode { return { kind: 'argumentValueNode', name: mainCase(name) }; diff --git a/src/nodes/contextualValueNodes/ConditionalValueNode.ts b/src/nodes/contextualValueNodes/ConditionalValueNode.ts index 24bf76eb0..ed12d779b 100644 --- a/src/nodes/contextualValueNodes/ConditionalValueNode.ts +++ b/src/nodes/contextualValueNodes/ConditionalValueNode.ts @@ -11,22 +11,39 @@ export type ConditionalValueBranch = InstructionInputValueNode; export const CONDITIONAL_VALUE_BRANCH_NODES = INSTRUCTION_INPUT_VALUE_NODE; -export type ConditionalValueNode = { +export interface ConditionalValueNode< + TCondition extends ResolverValueNode | AccountValueNode | ArgumentValueNode = + | ResolverValueNode + | AccountValueNode + | ArgumentValueNode, + TValue extends ValueNode | undefined = ValueNode | undefined, + TIfTrue extends ConditionalValueBranch | undefined = + | ConditionalValueBranch + | undefined, + TIfFalse extends ConditionalValueBranch | undefined = + | ConditionalValueBranch + | undefined, +> { readonly kind: 'conditionalValueNode'; // Children. - readonly condition: ResolverValueNode | AccountValueNode | ArgumentValueNode; - readonly value?: ValueNode; - readonly ifTrue?: ConditionalValueBranch; - readonly ifFalse?: ConditionalValueBranch; -}; + readonly condition: TCondition; + readonly value?: TValue; + readonly ifTrue?: TIfTrue; + readonly ifFalse?: TIfFalse; +} -export function conditionalValueNode(input: { - condition: ConditionalValueNode['condition']; - value?: ConditionalValueNode['value']; - ifTrue?: ConditionalValueNode['ifTrue']; - ifFalse?: ConditionalValueNode['ifFalse']; -}): ConditionalValueNode { +export function conditionalValueNode< + TCondition extends ResolverValueNode | AccountValueNode | ArgumentValueNode, + TValue extends ValueNode | undefined = undefined, + TIfTrue extends ConditionalValueBranch | undefined = undefined, + TIfFalse extends ConditionalValueBranch | undefined = undefined, +>(input: { + condition: TCondition; + value?: TValue; + ifTrue?: TIfTrue; + ifFalse?: TIfFalse; +}): ConditionalValueNode { return { kind: 'conditionalValueNode', condition: input.condition, diff --git a/src/nodes/contextualValueNodes/ContextualValueNode.ts b/src/nodes/contextualValueNodes/ContextualValueNode.ts index 05e9836cd..4e26a6af4 100644 --- a/src/nodes/contextualValueNodes/ContextualValueNode.ts +++ b/src/nodes/contextualValueNodes/ContextualValueNode.ts @@ -1,4 +1,3 @@ -import { getNodeKinds } from '../../shared/utils'; import type { ProgramLinkNode } from '../linkNodes/ProgramLinkNode'; import { VALUE_NODES, ValueNode } from '../valueNodes/ValueNode'; import type { AccountBumpValueNode } from './AccountBumpValueNode'; @@ -13,46 +12,40 @@ import type { ProgramIdValueNode } from './ProgramIdValueNode'; import type { ResolverValueNode } from './ResolverValueNode'; // Standalone Contextual Value Node Registration. - -export const STANDALONE_CONTEXTUAL_VALUE_NODES = { - accountBumpValueNode: {} as AccountBumpValueNode, - accountValueNode: {} as AccountValueNode, - argumentValueNode: {} as ArgumentValueNode, - conditionalValueNode: {} as ConditionalValueNode, - identityValueNode: {} as IdentityValueNode, - payerValueNode: {} as PayerValueNode, - pdaValueNode: {} as PdaValueNode, - programIdValueNode: {} as ProgramIdValueNode, - resolverValueNode: {} as ResolverValueNode, -}; - -export const STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS = getNodeKinds( - STANDALONE_CONTEXTUAL_VALUE_NODES -); -export type StandaloneContextualValueNodeKind = - (typeof STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS)[number]; export type StandaloneContextualValueNode = - (typeof STANDALONE_CONTEXTUAL_VALUE_NODES)[StandaloneContextualValueNodeKind]; + | AccountBumpValueNode + | AccountValueNode + | ArgumentValueNode + | ConditionalValueNode + | IdentityValueNode + | PayerValueNode + | PdaValueNode + | ProgramIdValueNode + | ResolverValueNode; +export const STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS = [ + 'accountBumpValueNode', + 'accountValueNode', + 'argumentValueNode', + 'conditionalValueNode', + 'identityValueNode', + 'payerValueNode', + 'pdaValueNode', + 'programIdValueNode', + 'resolverValueNode', +] satisfies readonly StandaloneContextualValueNode['kind'][]; +null as unknown as StandaloneContextualValueNode['kind'] satisfies (typeof STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS)[number]; // Contextual Value Node Registration. - -export const REGISTERED_CONTEXTUAL_VALUE_NODES = { - ...STANDALONE_CONTEXTUAL_VALUE_NODES, - - // The following are not valid standalone nodes. - pdaSeedValueNode: {} as PdaSeedValueNode, -}; - -export const REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS = getNodeKinds( - REGISTERED_CONTEXTUAL_VALUE_NODES -); -export type RegisteredContextualValueNodeKind = - (typeof REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS)[number]; export type RegisteredContextualValueNode = - (typeof REGISTERED_CONTEXTUAL_VALUE_NODES)[RegisteredContextualValueNodeKind]; + | StandaloneContextualValueNode + | PdaSeedValueNode; +export const REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS = [ + ...STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS, + 'pdaSeedValueNode', +] satisfies readonly RegisteredContextualValueNode['kind'][]; +null as unknown as RegisteredContextualValueNode['kind'] satisfies (typeof REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS)[number]; // Contextual Value Node Helpers. - export type ContextualValueNode = StandaloneContextualValueNode; export const CONTEXTUAL_VALUE_NODES = STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS; @@ -64,5 +57,5 @@ export type InstructionInputValueNode = export const INSTRUCTION_INPUT_VALUE_NODE = [ ...VALUE_NODES, ...CONTEXTUAL_VALUE_NODES, - 'programLinkNode' as const, -]; + 'programLinkNode', +] satisfies readonly InstructionInputValueNode['kind'][]; diff --git a/src/nodes/contextualValueNodes/IdentityValueNode.ts b/src/nodes/contextualValueNodes/IdentityValueNode.ts index c0deab57e..15325716c 100644 --- a/src/nodes/contextualValueNodes/IdentityValueNode.ts +++ b/src/nodes/contextualValueNodes/IdentityValueNode.ts @@ -1,6 +1,6 @@ -export type IdentityValueNode = { +export interface IdentityValueNode { readonly kind: 'identityValueNode'; -}; +} export function identityValueNode(): IdentityValueNode { return { kind: 'identityValueNode' }; diff --git a/src/nodes/contextualValueNodes/PayerValueNode.ts b/src/nodes/contextualValueNodes/PayerValueNode.ts index ee401ae14..162bcb9f2 100644 --- a/src/nodes/contextualValueNodes/PayerValueNode.ts +++ b/src/nodes/contextualValueNodes/PayerValueNode.ts @@ -1,6 +1,6 @@ -export type PayerValueNode = { +export interface PayerValueNode { readonly kind: 'payerValueNode'; -}; +} export function payerValueNode(): PayerValueNode { return { kind: 'payerValueNode' }; diff --git a/src/nodes/contextualValueNodes/PdaSeedValueNode.ts b/src/nodes/contextualValueNodes/PdaSeedValueNode.ts index 5b918a7e4..8f8cd2828 100644 --- a/src/nodes/contextualValueNodes/PdaSeedValueNode.ts +++ b/src/nodes/contextualValueNodes/PdaSeedValueNode.ts @@ -3,19 +3,26 @@ import { ValueNode } from '../valueNodes'; import { AccountValueNode } from './AccountValueNode'; import { ArgumentValueNode } from './ArgumentValueNode'; -export type PdaSeedValueNode = { +export interface PdaSeedValueNode< + TValue extends ValueNode | AccountValueNode | ArgumentValueNode = + | ValueNode + | AccountValueNode + | ArgumentValueNode, +> { readonly kind: 'pdaSeedValueNode'; // Children. - readonly value: ValueNode | AccountValueNode | ArgumentValueNode; + readonly value: TValue; // Data. readonly name: MainCaseString; -}; +} -export function pdaSeedValueNode( - name: string, - value: PdaSeedValueNode['value'] -): PdaSeedValueNode { +export function pdaSeedValueNode< + TValue extends ValueNode | AccountValueNode | ArgumentValueNode = + | ValueNode + | AccountValueNode + | ArgumentValueNode, +>(name: string, value: TValue): PdaSeedValueNode { return { kind: 'pdaSeedValueNode', name: mainCase(name), value }; } diff --git a/src/nodes/contextualValueNodes/PdaValueNode.ts b/src/nodes/contextualValueNodes/PdaValueNode.ts index d3d17c763..e1d1558e9 100644 --- a/src/nodes/contextualValueNodes/PdaValueNode.ts +++ b/src/nodes/contextualValueNodes/PdaValueNode.ts @@ -1,18 +1,20 @@ import { PdaLinkNode, pdaLinkNode } from '../linkNodes'; import { PdaSeedValueNode } from './PdaSeedValueNode'; -export type PdaValueNode = { +export interface PdaValueNode< + TSeeds extends PdaSeedValueNode[] = PdaSeedValueNode[], +> { readonly kind: 'pdaValueNode'; // Children. readonly pda: PdaLinkNode; - readonly seeds: PdaSeedValueNode[]; -}; + readonly seeds: TSeeds; +} -export function pdaValueNode( +export function pdaValueNode( pda: PdaLinkNode | string, - seeds: PdaSeedValueNode[] = [] -): PdaValueNode { + seeds: TSeeds = [] as PdaSeedValueNode[] as TSeeds +): PdaValueNode { return { kind: 'pdaValueNode', pda: typeof pda === 'string' ? pdaLinkNode(pda) : pda, diff --git a/src/nodes/contextualValueNodes/ProgramIdValueNode.ts b/src/nodes/contextualValueNodes/ProgramIdValueNode.ts index 67ff53140..ae10893d7 100644 --- a/src/nodes/contextualValueNodes/ProgramIdValueNode.ts +++ b/src/nodes/contextualValueNodes/ProgramIdValueNode.ts @@ -1,6 +1,6 @@ -export type ProgramIdValueNode = { +export interface ProgramIdValueNode { readonly kind: 'programIdValueNode'; -}; +} export function programIdValueNode(): ProgramIdValueNode { return { kind: 'programIdValueNode' }; diff --git a/src/nodes/contextualValueNodes/ResolverValueNode.ts b/src/nodes/contextualValueNodes/ResolverValueNode.ts index fe05e8ce0..e6c7a5d52 100644 --- a/src/nodes/contextualValueNodes/ResolverValueNode.ts +++ b/src/nodes/contextualValueNodes/ResolverValueNode.ts @@ -2,24 +2,31 @@ import { ImportFrom, MainCaseString, mainCase } from '../../shared'; import { AccountValueNode } from './AccountValueNode'; import { ArgumentValueNode } from './ArgumentValueNode'; -export type ResolverValueNode = { +export interface ResolverValueNode< + TDependsOn extends (AccountValueNode | ArgumentValueNode)[] = ( + | AccountValueNode + | ArgumentValueNode + )[], +> { readonly kind: 'resolverValueNode'; // Children. - readonly dependsOn?: (AccountValueNode | ArgumentValueNode)[]; + readonly dependsOn?: TDependsOn; // Data. readonly name: MainCaseString; readonly importFrom?: ImportFrom; -}; +} -export function resolverValueNode( +export function resolverValueNode< + const TDependsOn extends (AccountValueNode | ArgumentValueNode)[] = [], +>( name: string, options: { importFrom?: ResolverValueNode['importFrom']; - dependsOn?: ResolverValueNode['dependsOn']; + dependsOn?: TDependsOn; } = {} -): ResolverValueNode { +): ResolverValueNode { return { kind: 'resolverValueNode', name: mainCase(name), diff --git a/src/nodes/countNodes/CountNode.ts b/src/nodes/countNodes/CountNode.ts index 6401b3215..0414c6176 100644 --- a/src/nodes/countNodes/CountNode.ts +++ b/src/nodes/countNodes/CountNode.ts @@ -1,23 +1,19 @@ -import { getNodeKinds } from '../../shared/utils'; import type { FixedCountNode } from './FixedCountNode'; import type { PrefixedCountNode } from './PrefixedCountNode'; import type { RemainderCountNode } from './RemainderCountNode'; // Count Node Registration. - -export const REGISTERED_COUNT_NODES = { - fixedCountNode: {} as FixedCountNode, - remainderCountNode: {} as RemainderCountNode, - prefixedCountNode: {} as PrefixedCountNode, -}; - -export const REGISTERED_COUNT_NODE_KINDS = getNodeKinds(REGISTERED_COUNT_NODES); -export type RegisteredCountNodeKind = - (typeof REGISTERED_COUNT_NODE_KINDS)[number]; export type RegisteredCountNode = - (typeof REGISTERED_COUNT_NODES)[RegisteredCountNodeKind]; + | FixedCountNode + | RemainderCountNode + | PrefixedCountNode; +export const REGISTERED_COUNT_NODE_KINDS = [ + 'fixedCountNode', + 'remainderCountNode', + 'prefixedCountNode', +] satisfies readonly RegisteredCountNode['kind'][]; +null as unknown as RegisteredCountNode['kind'] satisfies (typeof REGISTERED_COUNT_NODE_KINDS)[number]; // Count Node Helpers. - export type CountNode = RegisteredCountNode; export const COUNT_NODES = REGISTERED_COUNT_NODE_KINDS; diff --git a/src/nodes/countNodes/FixedCountNode.ts b/src/nodes/countNodes/FixedCountNode.ts index 1a22c7287..a89f2f9ca 100644 --- a/src/nodes/countNodes/FixedCountNode.ts +++ b/src/nodes/countNodes/FixedCountNode.ts @@ -1,9 +1,9 @@ -export type FixedCountNode = { +export interface FixedCountNode { readonly kind: 'fixedCountNode'; // Data. readonly value: number; -}; +} export function fixedCountNode(value: number): FixedCountNode { return { kind: 'fixedCountNode', value }; diff --git a/src/nodes/countNodes/PrefixedCountNode.ts b/src/nodes/countNodes/PrefixedCountNode.ts index d0c24a549..d8322757c 100644 --- a/src/nodes/countNodes/PrefixedCountNode.ts +++ b/src/nodes/countNodes/PrefixedCountNode.ts @@ -1,12 +1,17 @@ -import { NumberTypeNode } from '../typeNodes'; +import { NumberTypeNode, ResolveNestedTypeNode } from '../typeNodes'; -export type PrefixedCountNode = { +export interface PrefixedCountNode< + TPrefix extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'prefixedCountNode'; // Children. - readonly prefix: NumberTypeNode; -}; + readonly prefix: TPrefix; +} -export function prefixedCountNode(prefix: NumberTypeNode): PrefixedCountNode { +export function prefixedCountNode< + TPrefix extends ResolveNestedTypeNode, +>(prefix: TPrefix): PrefixedCountNode { return { kind: 'prefixedCountNode', prefix }; } diff --git a/src/nodes/countNodes/RemainderCountNode.ts b/src/nodes/countNodes/RemainderCountNode.ts index ca31a3116..f18d1b884 100644 --- a/src/nodes/countNodes/RemainderCountNode.ts +++ b/src/nodes/countNodes/RemainderCountNode.ts @@ -1,6 +1,6 @@ -export type RemainderCountNode = { +export interface RemainderCountNode { readonly kind: 'remainderCountNode'; -}; +} export function remainderCountNode(): RemainderCountNode { return { kind: 'remainderCountNode' }; diff --git a/src/nodes/discriminatorNodes/ByteDiscriminatorNode.ts b/src/nodes/discriminatorNodes/ByteDiscriminatorNode.ts index 195e9a3ef..100bedb94 100644 --- a/src/nodes/discriminatorNodes/ByteDiscriminatorNode.ts +++ b/src/nodes/discriminatorNodes/ByteDiscriminatorNode.ts @@ -1,12 +1,12 @@ import { getBase58Encoder } from '@solana/codecs-strings'; -export type ByteDiscriminatorNode = { +export interface ByteDiscriminatorNode { readonly kind: 'byteDiscriminatorNode'; // Data. readonly bytes: number[]; readonly offset: number; -}; +} export function byteDiscriminatorNode( bytes: number[], diff --git a/src/nodes/discriminatorNodes/DiscriminatorNode.ts b/src/nodes/discriminatorNodes/DiscriminatorNode.ts index 119db426d..6581da59a 100644 --- a/src/nodes/discriminatorNodes/DiscriminatorNode.ts +++ b/src/nodes/discriminatorNodes/DiscriminatorNode.ts @@ -1,25 +1,19 @@ -import { getNodeKinds } from '../../shared/utils'; import type { ByteDiscriminatorNode } from './ByteDiscriminatorNode'; import type { FieldDiscriminatorNode } from './FieldDiscriminatorNode'; import type { SizeDiscriminatorNode } from './SizeDiscriminatorNode'; // Discriminator Node Registration. - -export const REGISTERED_DISCRIMINATOR_NODES = { - byteDiscriminatorNode: {} as ByteDiscriminatorNode, - fieldDiscriminatorNode: {} as FieldDiscriminatorNode, - sizeDiscriminatorNode: {} as SizeDiscriminatorNode, -}; - -export const REGISTERED_DISCRIMINATOR_NODE_KINDS = getNodeKinds( - REGISTERED_DISCRIMINATOR_NODES -); -export type RegisteredDiscriminatorNodeKind = - (typeof REGISTERED_DISCRIMINATOR_NODE_KINDS)[number]; export type RegisteredDiscriminatorNode = - (typeof REGISTERED_DISCRIMINATOR_NODES)[RegisteredDiscriminatorNodeKind]; + | ByteDiscriminatorNode + | FieldDiscriminatorNode + | SizeDiscriminatorNode; +export const REGISTERED_DISCRIMINATOR_NODE_KINDS = [ + 'byteDiscriminatorNode', + 'fieldDiscriminatorNode', + 'sizeDiscriminatorNode', +] satisfies readonly RegisteredDiscriminatorNode['kind'][]; +null as unknown as RegisteredDiscriminatorNode['kind'] satisfies (typeof REGISTERED_DISCRIMINATOR_NODE_KINDS)[number]; // Discriminator Node Helpers. - export type DiscriminatorNode = RegisteredDiscriminatorNode; export const DISCRIMINATOR_NODES = REGISTERED_DISCRIMINATOR_NODE_KINDS; diff --git a/src/nodes/discriminatorNodes/FieldDiscriminatorNode.ts b/src/nodes/discriminatorNodes/FieldDiscriminatorNode.ts index d40f01910..85f92f666 100644 --- a/src/nodes/discriminatorNodes/FieldDiscriminatorNode.ts +++ b/src/nodes/discriminatorNodes/FieldDiscriminatorNode.ts @@ -1,12 +1,12 @@ import { MainCaseString, mainCase } from '../../shared'; -export type FieldDiscriminatorNode = { +export interface FieldDiscriminatorNode { readonly kind: 'fieldDiscriminatorNode'; // Data. readonly name: MainCaseString; readonly offset: number; -}; +} export function fieldDiscriminatorNode( name: string, diff --git a/src/nodes/discriminatorNodes/SizeDiscriminatorNode.ts b/src/nodes/discriminatorNodes/SizeDiscriminatorNode.ts index 430b4e781..43009fd0b 100644 --- a/src/nodes/discriminatorNodes/SizeDiscriminatorNode.ts +++ b/src/nodes/discriminatorNodes/SizeDiscriminatorNode.ts @@ -1,9 +1,9 @@ -export type SizeDiscriminatorNode = { +export interface SizeDiscriminatorNode { readonly kind: 'sizeDiscriminatorNode'; // Data. readonly size: number; -}; +} export function sizeDiscriminatorNode(size: number): SizeDiscriminatorNode { return { kind: 'sizeDiscriminatorNode', size }; diff --git a/src/nodes/linkNodes/AccountLinkNode.ts b/src/nodes/linkNodes/AccountLinkNode.ts index 41c3e845c..ca9d27ec3 100644 --- a/src/nodes/linkNodes/AccountLinkNode.ts +++ b/src/nodes/linkNodes/AccountLinkNode.ts @@ -1,12 +1,12 @@ import { ImportFrom, MainCaseString, mainCase } from '../../shared'; -export type AccountLinkNode = { +export interface AccountLinkNode { readonly kind: 'accountLinkNode'; // Data. readonly name: MainCaseString; readonly importFrom?: ImportFrom; -}; +} export function accountLinkNode( name: string, diff --git a/src/nodes/linkNodes/DefinedTypeLinkNode.ts b/src/nodes/linkNodes/DefinedTypeLinkNode.ts index 6eab60d6b..92e45347c 100644 --- a/src/nodes/linkNodes/DefinedTypeLinkNode.ts +++ b/src/nodes/linkNodes/DefinedTypeLinkNode.ts @@ -1,12 +1,12 @@ import { ImportFrom, MainCaseString, mainCase } from '../../shared'; -export type DefinedTypeLinkNode = { +export interface DefinedTypeLinkNode { readonly kind: 'definedTypeLinkNode'; // Data. readonly name: MainCaseString; readonly importFrom?: ImportFrom; -}; +} export function definedTypeLinkNode( name: string, diff --git a/src/nodes/linkNodes/LinkNode.ts b/src/nodes/linkNodes/LinkNode.ts index 0270b44ac..3b2226779 100644 --- a/src/nodes/linkNodes/LinkNode.ts +++ b/src/nodes/linkNodes/LinkNode.ts @@ -1,25 +1,22 @@ -import { getNodeKinds } from '../../shared/utils'; import type { AccountLinkNode } from './AccountLinkNode'; import type { DefinedTypeLinkNode } from './DefinedTypeLinkNode'; import type { PdaLinkNode } from './PdaLinkNode'; import type { ProgramLinkNode } from './ProgramLinkNode'; // Link Node Registration. - -export const REGISTERED_LINK_NODES = { - programLinkNode: {} as ProgramLinkNode, - pdaLinkNode: {} as PdaLinkNode, - accountLinkNode: {} as AccountLinkNode, - definedTypeLinkNode: {} as DefinedTypeLinkNode, -}; - -export const REGISTERED_LINK_NODE_KINDS = getNodeKinds(REGISTERED_LINK_NODES); -export type RegisteredLinkNodeKind = - (typeof REGISTERED_LINK_NODE_KINDS)[number]; export type RegisteredLinkNode = - (typeof REGISTERED_LINK_NODES)[RegisteredLinkNodeKind]; + | ProgramLinkNode + | PdaLinkNode + | AccountLinkNode + | DefinedTypeLinkNode; +export const REGISTERED_LINK_NODE_KINDS = [ + 'programLinkNode', + 'pdaLinkNode', + 'accountLinkNode', + 'definedTypeLinkNode', +] satisfies readonly RegisteredLinkNode['kind'][]; +null as unknown as RegisteredLinkNode['kind'] satisfies (typeof REGISTERED_LINK_NODE_KINDS)[number]; // Link Node Helpers. - export type LinkNode = RegisteredLinkNode; export const LINK_NODES = REGISTERED_LINK_NODE_KINDS; diff --git a/src/nodes/linkNodes/PdaLinkNode.ts b/src/nodes/linkNodes/PdaLinkNode.ts index 5673ac77f..b1e972bed 100644 --- a/src/nodes/linkNodes/PdaLinkNode.ts +++ b/src/nodes/linkNodes/PdaLinkNode.ts @@ -1,12 +1,12 @@ import { ImportFrom, MainCaseString, mainCase } from '../../shared'; -export type PdaLinkNode = { +export interface PdaLinkNode { readonly kind: 'pdaLinkNode'; // Data. readonly name: MainCaseString; readonly importFrom?: ImportFrom; -}; +} export function pdaLinkNode( name: string, diff --git a/src/nodes/linkNodes/ProgramLinkNode.ts b/src/nodes/linkNodes/ProgramLinkNode.ts index 1a2fe7f04..96596453f 100644 --- a/src/nodes/linkNodes/ProgramLinkNode.ts +++ b/src/nodes/linkNodes/ProgramLinkNode.ts @@ -1,12 +1,12 @@ import { ImportFrom, MainCaseString, mainCase } from '../../shared'; -export type ProgramLinkNode = { +export interface ProgramLinkNode { readonly kind: 'programLinkNode'; // Data. readonly name: MainCaseString; readonly importFrom?: ImportFrom; -}; +} export function programLinkNode( name: string, diff --git a/src/nodes/pdaSeedNodes/ConstantPdaSeedNode.ts b/src/nodes/pdaSeedNodes/ConstantPdaSeedNode.ts index 77a195989..06366e1de 100644 --- a/src/nodes/pdaSeedNodes/ConstantPdaSeedNode.ts +++ b/src/nodes/pdaSeedNodes/ConstantPdaSeedNode.ts @@ -1,27 +1,24 @@ import { TypeNode, stringTypeNode } from '../typeNodes'; import { ValueNode, stringValueNode } from '../valueNodes'; -export type ConstantPdaSeedNode = { +export interface ConstantPdaSeedNode< + TType extends TypeNode = TypeNode, + TValue extends ValueNode = ValueNode, +> { readonly kind: 'constantPdaSeedNode'; // Children. - readonly type: TypeNode; - readonly value: ValueNode; -}; + readonly type: TType; + readonly value: TValue; +} -export function constantPdaSeedNode( - type: TypeNode, - value: ValueNode -): ConstantPdaSeedNode { +export function constantPdaSeedNode< + TType extends TypeNode, + TValue extends ValueNode, +>(type: TType, value: TValue): ConstantPdaSeedNode { return { kind: 'constantPdaSeedNode', type, value }; } -export function constantPdaSeedNodeFromString( - value: string -): ConstantPdaSeedNode { - return { - kind: 'constantPdaSeedNode', - type: stringTypeNode(), - value: stringValueNode(value), - }; +export function constantPdaSeedNodeFromString(value: string) { + return constantPdaSeedNode(stringTypeNode(), stringValueNode(value)); } diff --git a/src/nodes/pdaSeedNodes/PdaSeedNode.ts b/src/nodes/pdaSeedNodes/PdaSeedNode.ts index 4a73eb217..502e7135f 100644 --- a/src/nodes/pdaSeedNodes/PdaSeedNode.ts +++ b/src/nodes/pdaSeedNodes/PdaSeedNode.ts @@ -1,25 +1,19 @@ -import { getNodeKinds } from '../../shared/utils'; import type { ConstantPdaSeedNode } from './ConstantPdaSeedNode'; import type { ProgramIdPdaSeedNode } from './ProgramIdPdaSeedNode'; import type { VariablePdaSeedNode } from './VariablePdaSeedNode'; // Pda Seed Node Registration. - -export const REGISTERED_PDA_SEED_NODES = { - constantPdaSeedNode: {} as ConstantPdaSeedNode, - programIdPdaSeedNode: {} as ProgramIdPdaSeedNode, - variablePdaSeedNode: {} as VariablePdaSeedNode, -}; - -export const REGISTERED_PDA_SEED_NODE_KINDS = getNodeKinds( - REGISTERED_PDA_SEED_NODES -); -export type RegisteredPdaSeedNodeKind = - (typeof REGISTERED_PDA_SEED_NODE_KINDS)[number]; export type RegisteredPdaSeedNode = - (typeof REGISTERED_PDA_SEED_NODES)[RegisteredPdaSeedNodeKind]; + | ConstantPdaSeedNode + | ProgramIdPdaSeedNode + | VariablePdaSeedNode; +export const REGISTERED_PDA_SEED_NODE_KINDS = [ + 'constantPdaSeedNode', + 'programIdPdaSeedNode', + 'variablePdaSeedNode', +] satisfies readonly RegisteredPdaSeedNode['kind'][]; +null as unknown as RegisteredPdaSeedNode['kind'] satisfies (typeof REGISTERED_PDA_SEED_NODE_KINDS)[number]; // Pda Seed Node Helpers. - export type PdaSeedNode = RegisteredPdaSeedNode; export const PDA_SEED_NODES = REGISTERED_PDA_SEED_NODE_KINDS; diff --git a/src/nodes/pdaSeedNodes/ProgramIdPdaSeedNode.ts b/src/nodes/pdaSeedNodes/ProgramIdPdaSeedNode.ts index e33445feb..0aba2c23d 100644 --- a/src/nodes/pdaSeedNodes/ProgramIdPdaSeedNode.ts +++ b/src/nodes/pdaSeedNodes/ProgramIdPdaSeedNode.ts @@ -1,6 +1,6 @@ -export type ProgramIdPdaSeedNode = { +export interface ProgramIdPdaSeedNode { readonly kind: 'programIdPdaSeedNode'; -}; +} export function programIdPdaSeedNode(): ProgramIdPdaSeedNode { return { kind: 'programIdPdaSeedNode' }; diff --git a/src/nodes/pdaSeedNodes/VariablePdaSeedNode.ts b/src/nodes/pdaSeedNodes/VariablePdaSeedNode.ts index 167c550b6..07af3f6ad 100644 --- a/src/nodes/pdaSeedNodes/VariablePdaSeedNode.ts +++ b/src/nodes/pdaSeedNodes/VariablePdaSeedNode.ts @@ -1,22 +1,22 @@ import { MainCaseString, mainCase } from '../../shared'; import { TypeNode } from '../typeNodes'; -export type VariablePdaSeedNode = { +export interface VariablePdaSeedNode { readonly kind: 'variablePdaSeedNode'; // Children. - readonly type: TypeNode; + readonly type: TType; // Data. readonly name: MainCaseString; readonly docs: string[]; -}; +} -export function variablePdaSeedNode( +export function variablePdaSeedNode( name: string, - type: TypeNode, + type: TType, docs: string | string[] = [] -): VariablePdaSeedNode { +): VariablePdaSeedNode { return { kind: 'variablePdaSeedNode', name: mainCase(name), diff --git a/src/nodes/typeNodes/AmountTypeNode.ts b/src/nodes/typeNodes/AmountTypeNode.ts index 199d4c948..0a489c03a 100644 --- a/src/nodes/typeNodes/AmountTypeNode.ts +++ b/src/nodes/typeNodes/AmountTypeNode.ts @@ -1,20 +1,22 @@ +import { ResolveNestedTypeNode } from './TypeNode'; import { NumberTypeNode } from './NumberTypeNode'; -export type AmountTypeNode = { +export interface AmountTypeNode< + TNumber extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'amountTypeNode'; // Children. - readonly number: NumberTypeNode; + readonly number: TNumber; // Data. readonly decimals: number; readonly unit?: string; -}; +} -export function amountTypeNode( - number: NumberTypeNode, - decimals: number, - unit?: string -): AmountTypeNode { +export function amountTypeNode< + TNumber extends ResolveNestedTypeNode, +>(number: TNumber, decimals: number, unit?: string): AmountTypeNode { return { kind: 'amountTypeNode', number, decimals, unit }; } diff --git a/src/nodes/typeNodes/ArrayTypeNode.ts b/src/nodes/typeNodes/ArrayTypeNode.ts index 0a1f67f29..035a378e6 100644 --- a/src/nodes/typeNodes/ArrayTypeNode.ts +++ b/src/nodes/typeNodes/ArrayTypeNode.ts @@ -1,29 +1,33 @@ import type { IdlTypeArray, IdlTypeVec } from '../../idl'; import { CountNode, + PrefixedCountNode, fixedCountNode, prefixedCountNode, remainderCountNode, } from '../countNodes'; -import { numberTypeNode } from './NumberTypeNode'; +import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; -export type ArrayTypeNode = { +export interface ArrayTypeNode< + TItem extends TypeNode = TypeNode, + TCount extends CountNode = CountNode, +> { readonly kind: 'arrayTypeNode'; // Children. - readonly item: TypeNode; - readonly count: CountNode; -}; + readonly item: TItem; + readonly count: TCount; +} -export function arrayTypeNode( - item: TypeNode, - count?: CountNode -): ArrayTypeNode { +export function arrayTypeNode< + TItem extends TypeNode, + TCount extends CountNode = PrefixedCountNode>, +>(item: TItem, count?: TCount): ArrayTypeNode { return { kind: 'arrayTypeNode', item, - count: count ?? prefixedCountNode(numberTypeNode('u32')), + count: (count ?? prefixedCountNode(numberTypeNode('u32'))) as TCount, }; } diff --git a/src/nodes/typeNodes/BooleanTypeNode.ts b/src/nodes/typeNodes/BooleanTypeNode.ts index e6dc9f181..36ee8b3a7 100644 --- a/src/nodes/typeNodes/BooleanTypeNode.ts +++ b/src/nodes/typeNodes/BooleanTypeNode.ts @@ -1,12 +1,21 @@ +import { ResolveNestedTypeNode } from './TypeNode'; import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; -export type BooleanTypeNode = { +export interface BooleanTypeNode< + TSize extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'booleanTypeNode'; // Children. - readonly size: NumberTypeNode; -}; + readonly size: TSize; +} -export function booleanTypeNode(size?: NumberTypeNode): BooleanTypeNode { - return { kind: 'booleanTypeNode', size: size ?? numberTypeNode('u8') }; +export function booleanTypeNode< + TSize extends ResolveNestedTypeNode = NumberTypeNode<'u8'>, +>(size?: TSize): BooleanTypeNode { + return { + kind: 'booleanTypeNode', + size: (size ?? numberTypeNode('u8')) as TSize, + }; } diff --git a/src/nodes/typeNodes/BytesTypeNode.ts b/src/nodes/typeNodes/BytesTypeNode.ts index 964939137..f54f519be 100644 --- a/src/nodes/typeNodes/BytesTypeNode.ts +++ b/src/nodes/typeNodes/BytesTypeNode.ts @@ -1,6 +1,6 @@ -export type BytesTypeNode = { +export interface BytesTypeNode { readonly kind: 'bytesTypeNode'; -}; +} export function bytesTypeNode(): BytesTypeNode { return { kind: 'bytesTypeNode' }; diff --git a/src/nodes/typeNodes/DateTimeTypeNode.ts b/src/nodes/typeNodes/DateTimeTypeNode.ts index 39412249f..4753f648f 100644 --- a/src/nodes/typeNodes/DateTimeTypeNode.ts +++ b/src/nodes/typeNodes/DateTimeTypeNode.ts @@ -1,12 +1,19 @@ import { NumberTypeNode } from './NumberTypeNode'; +import { ResolveNestedTypeNode } from './TypeNode'; -export type DateTimeTypeNode = { +export interface DateTimeTypeNode< + TNumber extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'dateTimeTypeNode'; // Children. - readonly number: NumberTypeNode; -}; + readonly number: TNumber; +} -export function dateTimeTypeNode(number: NumberTypeNode): DateTimeTypeNode { +export function dateTimeTypeNode< + TNumber extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +>(number: TNumber): DateTimeTypeNode { return { kind: 'dateTimeTypeNode', number }; } diff --git a/src/nodes/typeNodes/EnumEmptyVariantTypeNode.ts b/src/nodes/typeNodes/EnumEmptyVariantTypeNode.ts index c51deffa4..4637442e8 100644 --- a/src/nodes/typeNodes/EnumEmptyVariantTypeNode.ts +++ b/src/nodes/typeNodes/EnumEmptyVariantTypeNode.ts @@ -1,12 +1,12 @@ import type { IdlTypeEnumVariant } from '../../idl'; import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../../shared'; -export type EnumEmptyVariantTypeNode = { +export interface EnumEmptyVariantTypeNode { readonly kind: 'enumEmptyVariantTypeNode'; // Data. readonly name: MainCaseString; -}; +} export function enumEmptyVariantTypeNode( name: string diff --git a/src/nodes/typeNodes/EnumStructVariantTypeNode.ts b/src/nodes/typeNodes/EnumStructVariantTypeNode.ts index 720b2e46a..3d3ad0118 100644 --- a/src/nodes/typeNodes/EnumStructVariantTypeNode.ts +++ b/src/nodes/typeNodes/EnumStructVariantTypeNode.ts @@ -1,21 +1,24 @@ +import type { ResolveNestedTypeNode } from './TypeNode'; import type { IdlTypeEnumField, IdlTypeEnumVariant } from '../../idl'; import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../../shared'; import { StructTypeNode, structTypeNodeFromIdl } from './StructTypeNode'; -export type EnumStructVariantTypeNode = { +export interface EnumStructVariantTypeNode< + TStruct extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'enumStructVariantTypeNode'; // Children. - readonly struct: StructTypeNode; + readonly struct: TStruct; // Data. readonly name: MainCaseString; -}; +} -export function enumStructVariantTypeNode( - name: string, - struct: StructTypeNode -): EnumStructVariantTypeNode { +export function enumStructVariantTypeNode< + TStruct extends ResolveNestedTypeNode, +>(name: string, struct: TStruct): EnumStructVariantTypeNode { if (!name) { throw new InvalidKinobiTreeError( 'EnumStructVariantTypeNode must have a name.' @@ -26,7 +29,7 @@ export function enumStructVariantTypeNode( export function enumStructVariantTypeNodeFromIdl( idl: IdlTypeEnumVariant -): EnumStructVariantTypeNode { +): EnumStructVariantTypeNode { const name = idl.name ?? ''; return enumStructVariantTypeNode( name, diff --git a/src/nodes/typeNodes/EnumTupleVariantTypeNode.ts b/src/nodes/typeNodes/EnumTupleVariantTypeNode.ts index a462143e6..6f7c5fbc8 100644 --- a/src/nodes/typeNodes/EnumTupleVariantTypeNode.ts +++ b/src/nodes/typeNodes/EnumTupleVariantTypeNode.ts @@ -1,21 +1,24 @@ +import type { ResolveNestedTypeNode } from './TypeNode'; import type { IdlType, IdlTypeEnumVariant } from '../../idl'; import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../../shared'; import { TupleTypeNode, tupleTypeNodeFromIdl } from './TupleTypeNode'; -export type EnumTupleVariantTypeNode = { +export interface EnumTupleVariantTypeNode< + TTuple extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'enumTupleVariantTypeNode'; // Children. - readonly tuple: TupleTypeNode; + readonly tuple: TTuple; // Data. readonly name: MainCaseString; -}; +} -export function enumTupleVariantTypeNode( - name: string, - tuple: TupleTypeNode -): EnumTupleVariantTypeNode { +export function enumTupleVariantTypeNode< + TTuple extends ResolveNestedTypeNode, +>(name: string, tuple: TTuple): EnumTupleVariantTypeNode { if (!name) { throw new InvalidKinobiTreeError( 'EnumTupleVariantTypeNode must have a name.' @@ -26,7 +29,7 @@ export function enumTupleVariantTypeNode( export function enumTupleVariantTypeNodeFromIdl( idl: IdlTypeEnumVariant -): EnumTupleVariantTypeNode { +): EnumTupleVariantTypeNode { const name = idl.name ?? ''; return enumTupleVariantTypeNode( name, diff --git a/src/nodes/typeNodes/EnumTypeNode.ts b/src/nodes/typeNodes/EnumTypeNode.ts index 1bdc87f35..ece716288 100644 --- a/src/nodes/typeNodes/EnumTypeNode.ts +++ b/src/nodes/typeNodes/EnumTypeNode.ts @@ -1,3 +1,4 @@ +import type { ResolveNestedTypeNode } from './TypeNode'; import type { IdlTypeEnum } from '../../idl'; import { enumEmptyVariantTypeNodeFromIdl } from './EnumEmptyVariantTypeNode'; import { enumStructVariantTypeNodeFromIdl } from './EnumStructVariantTypeNode'; @@ -5,26 +6,35 @@ import { enumTupleVariantTypeNodeFromIdl } from './EnumTupleVariantTypeNode'; import type { EnumVariantTypeNode } from './EnumVariantTypeNode'; import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; -export type EnumTypeNode = { +export interface EnumTypeNode< + TVariants extends EnumVariantTypeNode[] = EnumVariantTypeNode[], + TSize extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'enumTypeNode'; // Children. - readonly variants: EnumVariantTypeNode[]; - readonly size: NumberTypeNode; -}; + readonly variants: TVariants; + readonly size: TSize; +} -export function enumTypeNode( - variants: EnumVariantTypeNode[], - options: { size?: NumberTypeNode } = {} -): EnumTypeNode { +export function enumTypeNode< + const TVariants extends EnumVariantTypeNode[], + TSize extends ResolveNestedTypeNode = NumberTypeNode<'u8'>, +>( + variants: TVariants, + options: { size?: TSize } = {} +): EnumTypeNode { return { kind: 'enumTypeNode', variants, - size: options.size ?? numberTypeNode('u8'), + size: (options.size ?? numberTypeNode('u8')) as TSize, }; } -export function enumTypeNodeFromIdl(idl: IdlTypeEnum): EnumTypeNode { +export function enumTypeNodeFromIdl( + idl: IdlTypeEnum +): EnumTypeNode { const variants = idl.variants.map((variant): EnumVariantTypeNode => { if (!variant.fields || variant.fields.length <= 0) { return enumEmptyVariantTypeNodeFromIdl(variant); diff --git a/src/nodes/typeNodes/EnumVariantTypeNode.ts b/src/nodes/typeNodes/EnumVariantTypeNode.ts index 94fbf3223..e501d9d49 100644 --- a/src/nodes/typeNodes/EnumVariantTypeNode.ts +++ b/src/nodes/typeNodes/EnumVariantTypeNode.ts @@ -12,7 +12,8 @@ const ENUM_VARIANT_TYPE_NODES_INTERNAL = [ 'enumEmptyVariantTypeNode', 'enumStructVariantTypeNode', 'enumTupleVariantTypeNode', -] as const; +] as const satisfies readonly EnumVariantTypeNode['kind'][]; +null as unknown as EnumVariantTypeNode['kind'] satisfies (typeof ENUM_VARIANT_TYPE_NODES_INTERNAL)[number]; export const ENUM_VARIANT_TYPE_NODES = ENUM_VARIANT_TYPE_NODES_INTERNAL as Mutable< diff --git a/src/nodes/typeNodes/FixedSizeTypeNode.ts b/src/nodes/typeNodes/FixedSizeTypeNode.ts index a6e097843..889999f13 100644 --- a/src/nodes/typeNodes/FixedSizeTypeNode.ts +++ b/src/nodes/typeNodes/FixedSizeTypeNode.ts @@ -1,18 +1,18 @@ import { TypeNode } from './TypeNode'; -export type FixedSizeTypeNode = { +export interface FixedSizeTypeNode { readonly kind: 'fixedSizeTypeNode'; // Children. - readonly type: TypeNode; + readonly type: TType; // Data. readonly size: number; -}; +} -export function fixedSizeTypeNode( - type: TypeNode, +export function fixedSizeTypeNode( + type: TType, size: number -): FixedSizeTypeNode { +): FixedSizeTypeNode { return { kind: 'fixedSizeTypeNode', type, size }; } diff --git a/src/nodes/typeNodes/MapTypeNode.ts b/src/nodes/typeNodes/MapTypeNode.ts index d4b0aa885..06eb3bb67 100644 --- a/src/nodes/typeNodes/MapTypeNode.ts +++ b/src/nodes/typeNodes/MapTypeNode.ts @@ -1,36 +1,45 @@ import type { IdlTypeMap } from '../../idl'; import { CountNode, + PrefixedCountNode, fixedCountNode, prefixedCountNode, remainderCountNode, } from '../countNodes'; -import { numberTypeNode } from './NumberTypeNode'; +import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; -export type MapTypeNode = { +export interface MapTypeNode< + TKey extends TypeNode = TypeNode, + TValue extends TypeNode = TypeNode, + TCount extends CountNode = CountNode, +> { readonly kind: 'mapTypeNode'; // Children. - readonly key: TypeNode; - readonly value: TypeNode; - readonly count: CountNode; + readonly key: TKey; + readonly value: TValue; + readonly count: TCount; // Data. readonly idlMap: 'hashMap' | 'bTreeMap'; -}; +} -export function mapTypeNode( - key: TypeNode, - value: TypeNode, - count?: CountNode, +export function mapTypeNode< + TKey extends TypeNode = TypeNode, + TValue extends TypeNode = TypeNode, + TCount extends CountNode = PrefixedCountNode>, +>( + key: TKey, + value: TValue, + count?: TCount, idlMap?: MapTypeNode['idlMap'] -): MapTypeNode { +): MapTypeNode { return { kind: 'mapTypeNode', key, value, - count: count ?? prefixedCountNode(numberTypeNode('u32')), + count: (count ?? prefixedCountNode(numberTypeNode('u32'))) as TCount, idlMap: idlMap ?? 'hashMap', }; } diff --git a/src/nodes/typeNodes/NumberTypeNode.ts b/src/nodes/typeNodes/NumberTypeNode.ts index e61518987..8b8c08110 100644 --- a/src/nodes/typeNodes/NumberTypeNode.ts +++ b/src/nodes/typeNodes/NumberTypeNode.ts @@ -12,18 +12,18 @@ export type NumberFormat = | 'f32' | 'f64'; -export type NumberTypeNode = { +export interface NumberTypeNode { readonly kind: 'numberTypeNode'; // Data. - readonly format: NumberFormat; + readonly format: TFormat; readonly endian: 'le' | 'be'; -}; +} -export function numberTypeNode( - format: NumberFormat, +export function numberTypeNode( + format: TFormat, endian: 'le' | 'be' = 'le' -): NumberTypeNode { +): NumberTypeNode { return { kind: 'numberTypeNode', format, endian }; } diff --git a/src/nodes/typeNodes/OptionTypeNode.ts b/src/nodes/typeNodes/OptionTypeNode.ts index 789b889b7..48d42a4ab 100644 --- a/src/nodes/typeNodes/OptionTypeNode.ts +++ b/src/nodes/typeNodes/OptionTypeNode.ts @@ -1,31 +1,42 @@ import type { IdlTypeOption } from '../../idl'; import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; -import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; +import { + ResolveNestedTypeNode, + TypeNode, + createTypeNodeFromIdl, +} from './TypeNode'; -export type OptionTypeNode = { +export interface OptionTypeNode< + TItem extends TypeNode = TypeNode, + TPrefix extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'optionTypeNode'; // Children. - readonly item: TypeNode; - readonly prefix: NumberTypeNode; + readonly item: TItem; + readonly prefix: TPrefix; // Data. readonly fixed: boolean; readonly idlOption: 'option' | 'coption'; -}; +} -export function optionTypeNode( - item: TypeNode, +export function optionTypeNode< + TItem extends TypeNode, + TPrefix extends ResolveNestedTypeNode = NumberTypeNode<'u8'>, +>( + item: TItem, options: { - readonly prefix?: NumberTypeNode; + readonly prefix?: TPrefix; readonly fixed?: boolean; readonly idlOption?: OptionTypeNode['idlOption']; } = {} -): OptionTypeNode { +): OptionTypeNode { return { kind: 'optionTypeNode', item, - prefix: options.prefix ?? numberTypeNode('u8'), + prefix: (options.prefix ?? numberTypeNode('u8')) as TPrefix, fixed: options.fixed ?? false, idlOption: options.idlOption ?? 'option', }; diff --git a/src/nodes/typeNodes/PublicKeyTypeNode.ts b/src/nodes/typeNodes/PublicKeyTypeNode.ts index 9385322be..510377973 100644 --- a/src/nodes/typeNodes/PublicKeyTypeNode.ts +++ b/src/nodes/typeNodes/PublicKeyTypeNode.ts @@ -1,6 +1,6 @@ -export type PublicKeyTypeNode = { +export interface PublicKeyTypeNode { readonly kind: 'publicKeyTypeNode'; -}; +} export function publicKeyTypeNode(): PublicKeyTypeNode { return { kind: 'publicKeyTypeNode' }; diff --git a/src/nodes/typeNodes/SetTypeNode.ts b/src/nodes/typeNodes/SetTypeNode.ts index 257cc5814..343120652 100644 --- a/src/nodes/typeNodes/SetTypeNode.ts +++ b/src/nodes/typeNodes/SetTypeNode.ts @@ -1,33 +1,40 @@ import type { IdlTypeSet } from '../../idl'; import { CountNode, + PrefixedCountNode, fixedCountNode, prefixedCountNode, remainderCountNode, } from '../countNodes'; -import { numberTypeNode } from './NumberTypeNode'; +import { NumberTypeNode, numberTypeNode } from './NumberTypeNode'; import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; -export type SetTypeNode = { +export interface SetTypeNode< + TItem extends TypeNode = TypeNode, + TCount extends CountNode = CountNode, +> { readonly kind: 'setTypeNode'; // Children. - readonly item: TypeNode; - readonly count: CountNode; + readonly item: TItem; + readonly count: TCount; // Data. readonly idlSet: 'hashSet' | 'bTreeSet'; -}; +} -export function setTypeNode( - item: TypeNode, - count?: CountNode, +export function setTypeNode< + TItem extends TypeNode = TypeNode, + TCount extends CountNode = PrefixedCountNode>, +>( + item: TItem, + count?: TCount, idlSet?: SetTypeNode['idlSet'] -): SetTypeNode { +): SetTypeNode { return { kind: 'setTypeNode', item, - count: count ?? prefixedCountNode(numberTypeNode('u32')), + count: (count ?? prefixedCountNode(numberTypeNode('u32'))) as TCount, idlSet: idlSet ?? 'hashSet', }; } diff --git a/src/nodes/typeNodes/SizePrefixTypeNode.ts b/src/nodes/typeNodes/SizePrefixTypeNode.ts index bb58729e1..331c42acd 100644 --- a/src/nodes/typeNodes/SizePrefixTypeNode.ts +++ b/src/nodes/typeNodes/SizePrefixTypeNode.ts @@ -1,17 +1,22 @@ import { NumberTypeNode } from './NumberTypeNode'; -import { TypeNode } from './TypeNode'; +import { ResolveNestedTypeNode, TypeNode } from './TypeNode'; -export type SizePrefixTypeNode = { +export interface SizePrefixTypeNode< + TType extends TypeNode = TypeNode, + TPrefix extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'sizePrefixTypeNode'; // Children. - readonly type: TypeNode; - readonly prefix: NumberTypeNode; -}; + readonly type: TType; + readonly prefix: TPrefix; +} -export function sizePrefixTypeNode( - type: TypeNode, - prefix: NumberTypeNode -): SizePrefixTypeNode { +export function sizePrefixTypeNode< + TType extends TypeNode = TypeNode, + TPrefix extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +>(type: TType, prefix: TPrefix): SizePrefixTypeNode { return { kind: 'sizePrefixTypeNode', type, prefix }; } diff --git a/src/nodes/typeNodes/SolAmountTypeNode.ts b/src/nodes/typeNodes/SolAmountTypeNode.ts index f06a02852..9fdf39294 100644 --- a/src/nodes/typeNodes/SolAmountTypeNode.ts +++ b/src/nodes/typeNodes/SolAmountTypeNode.ts @@ -1,12 +1,18 @@ +import { ResolveNestedTypeNode } from './TypeNode'; import { NumberTypeNode } from './NumberTypeNode'; -export type SolAmountTypeNode = { +export interface SolAmountTypeNode< + TNumber extends + ResolveNestedTypeNode = ResolveNestedTypeNode, +> { readonly kind: 'solAmountTypeNode'; // Children. - readonly number: NumberTypeNode; -}; + readonly number: TNumber; +} -export function solAmountTypeNode(number: NumberTypeNode): SolAmountTypeNode { +export function solAmountTypeNode< + TNumber extends ResolveNestedTypeNode, +>(number: TNumber): SolAmountTypeNode { return { kind: 'solAmountTypeNode', number }; } diff --git a/src/nodes/typeNodes/StringTypeNode.ts b/src/nodes/typeNodes/StringTypeNode.ts index 2548463a8..6a06f80e3 100644 --- a/src/nodes/typeNodes/StringTypeNode.ts +++ b/src/nodes/typeNodes/StringTypeNode.ts @@ -1,12 +1,19 @@ export type StringEncoding = 'utf8' | 'base16' | 'base58' | 'base64'; -export type StringTypeNode = { +export interface StringTypeNode< + TEncoding extends StringEncoding = StringEncoding, +> { readonly kind: 'stringTypeNode'; // Data. - readonly encoding: StringEncoding; -}; + readonly encoding: TEncoding; +} -export function stringTypeNode(encoding?: StringEncoding): StringTypeNode { - return { kind: 'stringTypeNode', encoding: encoding ?? 'utf8' }; +export function stringTypeNode( + encoding?: TEncoding +): StringTypeNode { + return { + kind: 'stringTypeNode', + encoding: (encoding ?? 'utf8') as TEncoding, + }; } diff --git a/src/nodes/typeNodes/StructFieldTypeNode.ts b/src/nodes/typeNodes/StructFieldTypeNode.ts index 81cc4127d..66098ccd1 100644 --- a/src/nodes/typeNodes/StructFieldTypeNode.ts +++ b/src/nodes/typeNodes/StructFieldTypeNode.ts @@ -3,30 +3,39 @@ import { InvalidKinobiTreeError, MainCaseString, mainCase } from '../../shared'; import { ValueNode } from '../valueNodes'; import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; -export type StructFieldTypeNode = { +export interface StructFieldTypeNode< + TType extends TypeNode = TypeNode, + TDefaultValue extends ValueNode | undefined = ValueNode | undefined, +> { readonly kind: 'structFieldTypeNode'; // Children. - readonly type: TypeNode; - readonly defaultValue?: ValueNode; + readonly type: TType; + readonly defaultValue?: TDefaultValue; // Data. readonly name: MainCaseString; readonly docs: string[]; readonly defaultValueStrategy?: 'optional' | 'omitted'; -}; +} -export type StructFieldTypeNodeInput = { +export type StructFieldTypeNodeInput< + TType extends TypeNode = TypeNode, + TDefaultValue extends ValueNode | undefined = ValueNode | undefined, +> = { readonly name: string; - readonly type: TypeNode; + readonly type: TType; readonly docs?: string[]; - readonly defaultValue?: ValueNode; + readonly defaultValue?: TDefaultValue; readonly defaultValueStrategy?: 'optional' | 'omitted'; }; -export function structFieldTypeNode( - input: StructFieldTypeNodeInput -): StructFieldTypeNode { +export function structFieldTypeNode< + TType extends TypeNode, + TDefaultValue extends ValueNode | undefined = undefined, +>( + input: StructFieldTypeNodeInput +): StructFieldTypeNode { if (!input.name) { throw new InvalidKinobiTreeError('StructFieldTypeNode must have a name.'); } diff --git a/src/nodes/typeNodes/StructTypeNode.ts b/src/nodes/typeNodes/StructTypeNode.ts index 76fe0627e..2f9cf4b2e 100644 --- a/src/nodes/typeNodes/StructTypeNode.ts +++ b/src/nodes/typeNodes/StructTypeNode.ts @@ -4,14 +4,18 @@ import { structFieldTypeNodeFromIdl, } from './StructFieldTypeNode'; -export type StructTypeNode = { +export interface StructTypeNode< + TFields extends StructFieldTypeNode[] = StructFieldTypeNode[], +> { readonly kind: 'structTypeNode'; // Children. - readonly fields: StructFieldTypeNode[]; -}; + readonly fields: TFields; +} -export function structTypeNode(fields: StructFieldTypeNode[]): StructTypeNode { +export function structTypeNode< + const TFields extends StructFieldTypeNode[] = StructFieldTypeNode[], +>(fields: TFields): StructTypeNode { return { kind: 'structTypeNode', fields }; } diff --git a/src/nodes/typeNodes/TupleTypeNode.ts b/src/nodes/typeNodes/TupleTypeNode.ts index 0994f28d1..31f5123af 100644 --- a/src/nodes/typeNodes/TupleTypeNode.ts +++ b/src/nodes/typeNodes/TupleTypeNode.ts @@ -1,16 +1,16 @@ import type { IdlTypeTuple } from '../../idl'; import { TypeNode, createTypeNodeFromIdl } from './TypeNode'; -export type TupleTypeNode = { +export interface TupleTypeNode { readonly kind: 'tupleTypeNode'; // Children. - readonly items: TypeNode[]; -}; + readonly items: TItems; +} -export function tupleTypeNode( - items: [...TItems] -): TupleTypeNode & { readonly items: [...TItems] } { +export function tupleTypeNode( + items: TItems +): TupleTypeNode { return { kind: 'tupleTypeNode', items }; } diff --git a/src/nodes/typeNodes/TypeNode.ts b/src/nodes/typeNodes/TypeNode.ts index 30e441a51..1a781c4da 100644 --- a/src/nodes/typeNodes/TypeNode.ts +++ b/src/nodes/typeNodes/TypeNode.ts @@ -1,5 +1,4 @@ import { IDL_TYPE_LEAVES, IdlType } from '../../idl'; -import { getNodeKinds } from '../../shared/utils'; import { DefinedTypeLinkNode, definedTypeLinkNode, @@ -27,62 +26,71 @@ import { StructTypeNode, structTypeNodeFromIdl } from './StructTypeNode'; import { TupleTypeNode, tupleTypeNodeFromIdl } from './TupleTypeNode'; // Standalone Type Node Registration. - -export const STANDALONE_TYPE_NODES = { - amountTypeNode: {} as AmountTypeNode, - arrayTypeNode: {} as ArrayTypeNode, - booleanTypeNode: {} as BooleanTypeNode, - bytesTypeNode: {} as BytesTypeNode, - dateTimeTypeNode: {} as DateTimeTypeNode, - enumTypeNode: {} as EnumTypeNode, - fixedSizeTypeNode: {} as FixedSizeTypeNode, - mapTypeNode: {} as MapTypeNode, - numberTypeNode: {} as NumberTypeNode, - optionTypeNode: {} as OptionTypeNode, - publicKeyTypeNode: {} as PublicKeyTypeNode, - setTypeNode: {} as SetTypeNode, - sizePrefixTypeNode: {} as SizePrefixTypeNode, - solAmountTypeNode: {} as SolAmountTypeNode, - stringTypeNode: {} as StringTypeNode, - structTypeNode: {} as StructTypeNode, - tupleTypeNode: {} as TupleTypeNode, -}; - -export const STANDALONE_TYPE_NODE_KINDS = getNodeKinds(STANDALONE_TYPE_NODES); -export type StandaloneTypeNodeKind = - (typeof STANDALONE_TYPE_NODE_KINDS)[number]; export type StandaloneTypeNode = - (typeof STANDALONE_TYPE_NODES)[StandaloneTypeNodeKind]; + | AmountTypeNode + | ArrayTypeNode + | BooleanTypeNode + | BytesTypeNode + | DateTimeTypeNode + | EnumTypeNode + | FixedSizeTypeNode + | MapTypeNode + | NumberTypeNode + | OptionTypeNode + | PublicKeyTypeNode + | SetTypeNode + | SizePrefixTypeNode + | SolAmountTypeNode + | StringTypeNode + | StructTypeNode + | TupleTypeNode; +export const STANDALONE_TYPE_NODE_KINDS = [ + 'amountTypeNode', + 'arrayTypeNode', + 'booleanTypeNode', + 'bytesTypeNode', + 'dateTimeTypeNode', + 'enumTypeNode', + 'fixedSizeTypeNode', + 'mapTypeNode', + 'numberTypeNode', + 'optionTypeNode', + 'publicKeyTypeNode', + 'setTypeNode', + 'sizePrefixTypeNode', + 'solAmountTypeNode', + 'stringTypeNode', + 'structTypeNode', + 'tupleTypeNode', +] satisfies readonly StandaloneTypeNode['kind'][]; +null as unknown as StandaloneTypeNode['kind'] satisfies (typeof STANDALONE_TYPE_NODE_KINDS)[number]; // Type Node Registration. - -export const REGISTERED_TYPE_NODES = { - ...STANDALONE_TYPE_NODES, - - // The following are not valid standalone nodes. - structFieldTypeNode: {} as StructFieldTypeNode, - enumEmptyVariantTypeNode: {} as EnumEmptyVariantTypeNode, - enumStructVariantTypeNode: {} as EnumStructVariantTypeNode, - enumTupleVariantTypeNode: {} as EnumTupleVariantTypeNode, -}; - -export const REGISTERED_TYPE_NODE_KINDS = getNodeKinds(REGISTERED_TYPE_NODES); -export type RegisteredTypeNodeKind = - (typeof REGISTERED_TYPE_NODE_KINDS)[number]; export type RegisteredTypeNode = - (typeof REGISTERED_TYPE_NODES)[RegisteredTypeNodeKind]; + | StandaloneTypeNode + | StructFieldTypeNode + | EnumEmptyVariantTypeNode + | EnumStructVariantTypeNode + | EnumTupleVariantTypeNode; +export const REGISTERED_TYPE_NODE_KINDS = [ + ...STANDALONE_TYPE_NODE_KINDS, + 'structFieldTypeNode', + 'enumEmptyVariantTypeNode', + 'enumStructVariantTypeNode', + 'enumTupleVariantTypeNode', +] satisfies readonly RegisteredTypeNode['kind'][]; +null as unknown as RegisteredTypeNode['kind'] satisfies (typeof REGISTERED_TYPE_NODE_KINDS)[number]; // Type Node Helpers. // This only includes type nodes that can be used as standalone types. // E.g. this excludes structFieldTypeNode, enumEmptyVariantTypeNode, etc. // It also includes the definedTypeLinkNode to compose types. +export type TypeNode = StandaloneTypeNode | DefinedTypeLinkNode; export const TYPE_NODES = [ ...STANDALONE_TYPE_NODE_KINDS, - 'definedTypeLinkNode' as const, -]; -export type TypeNodeKind = (typeof TYPE_NODES)[number]; -export type TypeNode = StandaloneTypeNode | DefinedTypeLinkNode; + 'definedTypeLinkNode', +] satisfies readonly TypeNode['kind'][]; export type ResolveNestedTypeNode = | TType @@ -90,16 +98,42 @@ export type ResolveNestedTypeNode = type: ResolveNestedTypeNode; }); -export function resolveNestedTypeNode(typeNode: TypeNode): TypeNode { +export function resolveNestedTypeNode( + typeNode: ResolveNestedTypeNode +): TType { switch (typeNode.kind) { case 'fixedSizeTypeNode': case 'sizePrefixTypeNode': - return resolveNestedTypeNode(typeNode.type); + return resolveNestedTypeNode( + typeNode.type as ResolveNestedTypeNode + ); default: return typeNode; } } +export function transformNestedTypeNode< + TFrom extends TypeNode, + TTo extends TypeNode, +>( + typeNode: ResolveNestedTypeNode, + map: (type: TFrom) => TTo +): ResolveNestedTypeNode { + switch (typeNode.kind) { + case 'fixedSizeTypeNode': + case 'sizePrefixTypeNode': + return { + ...typeNode, + type: transformNestedTypeNode( + typeNode.type as ResolveNestedTypeNode, + map + ), + } as ResolveNestedTypeNode; + default: + return map(typeNode); + } +} + function isArrayOfSize(array: any, size: number): boolean { return Array.isArray(array) && array.length === size; } diff --git a/src/nodes/valueNodes/ArrayValueNode.ts b/src/nodes/valueNodes/ArrayValueNode.ts index 31a04c6cb..32bae09bb 100644 --- a/src/nodes/valueNodes/ArrayValueNode.ts +++ b/src/nodes/valueNodes/ArrayValueNode.ts @@ -1,12 +1,14 @@ import { ValueNode } from './ValueNode'; -export type ArrayValueNode = { +export interface ArrayValueNode { readonly kind: 'arrayValueNode'; // Children. - readonly items: ValueNode[]; -}; + readonly items: TItems; +} -export function arrayValueNode(items: ValueNode[]): ArrayValueNode { +export function arrayValueNode( + items: TItems +): ArrayValueNode { return { kind: 'arrayValueNode', items }; } diff --git a/src/nodes/valueNodes/BooleanValueNode.ts b/src/nodes/valueNodes/BooleanValueNode.ts index d460245ed..a593e6e2c 100644 --- a/src/nodes/valueNodes/BooleanValueNode.ts +++ b/src/nodes/valueNodes/BooleanValueNode.ts @@ -1,9 +1,9 @@ -export type BooleanValueNode = { +export interface BooleanValueNode { readonly kind: 'booleanValueNode'; // Data. readonly boolean: boolean; -}; +} export function booleanValueNode(boolean: boolean): BooleanValueNode { return { kind: 'booleanValueNode', boolean }; diff --git a/src/nodes/valueNodes/EnumValueNode.ts b/src/nodes/valueNodes/EnumValueNode.ts index 3d8a1d96a..65dade483 100644 --- a/src/nodes/valueNodes/EnumValueNode.ts +++ b/src/nodes/valueNodes/EnumValueNode.ts @@ -3,26 +3,36 @@ import { DefinedTypeLinkNode, definedTypeLinkNode } from '../linkNodes'; import { StructValueNode } from './StructValueNode'; import { TupleValueNode } from './TupleValueNode'; -export type EnumValueNode = { +export interface EnumValueNode< + TEnum extends DefinedTypeLinkNode = DefinedTypeLinkNode, + TValue extends StructValueNode | TupleValueNode | undefined = + | StructValueNode + | TupleValueNode + | undefined, +> { readonly kind: 'enumValueNode'; // Children. - readonly enum: DefinedTypeLinkNode; - readonly value?: StructValueNode | TupleValueNode; + readonly enum: TEnum; + readonly value?: TValue; // Data. readonly variant: MainCaseString; -}; +} -export function enumValueNode( - enumLink: DefinedTypeLinkNode | string, +export function enumValueNode< + TEnum extends DefinedTypeLinkNode = DefinedTypeLinkNode, + TValue extends StructValueNode | TupleValueNode | undefined = undefined, +>( + enumLink: TEnum | string, variant: string, - value?: StructValueNode | TupleValueNode -): EnumValueNode { + value?: TValue +): EnumValueNode { return { kind: 'enumValueNode', - enum: - typeof enumLink === 'string' ? definedTypeLinkNode(enumLink) : enumLink, + enum: (typeof enumLink === 'string' + ? definedTypeLinkNode(enumLink) + : enumLink) as TEnum, variant: mainCase(variant), value, }; diff --git a/src/nodes/valueNodes/MapEntryValueNode.ts b/src/nodes/valueNodes/MapEntryValueNode.ts index 4e6c37275..a3ae76f20 100644 --- a/src/nodes/valueNodes/MapEntryValueNode.ts +++ b/src/nodes/valueNodes/MapEntryValueNode.ts @@ -1,16 +1,19 @@ import { ValueNode } from './ValueNode'; -export type MapEntryValueNode = { +export interface MapEntryValueNode< + TKey extends ValueNode = ValueNode, + TValue extends ValueNode = ValueNode, +> { readonly kind: 'mapEntryValueNode'; // Children. - readonly key: ValueNode; - readonly value: ValueNode; -}; + readonly key: TKey; + readonly value: TValue; +} -export function mapEntryValueNode( - key: ValueNode, - value: ValueNode -): MapEntryValueNode { +export function mapEntryValueNode< + TKey extends ValueNode, + TValue extends ValueNode, +>(key: TKey, value: TValue): MapEntryValueNode { return { kind: 'mapEntryValueNode', key, value }; } diff --git a/src/nodes/valueNodes/MapValueNode.ts b/src/nodes/valueNodes/MapValueNode.ts index 1f7d7b18c..920b33873 100644 --- a/src/nodes/valueNodes/MapValueNode.ts +++ b/src/nodes/valueNodes/MapValueNode.ts @@ -1,12 +1,16 @@ import { MapEntryValueNode } from './MapEntryValueNode'; -export type MapValueNode = { +export interface MapValueNode< + TEntries extends MapEntryValueNode[] = MapEntryValueNode[], +> { readonly kind: 'mapValueNode'; // Children. - readonly entries: MapEntryValueNode[]; -}; + readonly entries: TEntries; +} -export function mapValueNode(entries: MapEntryValueNode[]): MapValueNode { +export function mapValueNode( + entries: TEntries +): MapValueNode { return { kind: 'mapValueNode', entries }; } diff --git a/src/nodes/valueNodes/NoneValueNode.ts b/src/nodes/valueNodes/NoneValueNode.ts index 59940d6fb..bcc1ef14c 100644 --- a/src/nodes/valueNodes/NoneValueNode.ts +++ b/src/nodes/valueNodes/NoneValueNode.ts @@ -1,6 +1,6 @@ -export type NoneValueNode = { +export interface NoneValueNode { readonly kind: 'noneValueNode'; -}; +} export function noneValueNode(): NoneValueNode { return { kind: 'noneValueNode' }; diff --git a/src/nodes/valueNodes/NumberValueNode.ts b/src/nodes/valueNodes/NumberValueNode.ts index 7661f95e8..9269ddd83 100644 --- a/src/nodes/valueNodes/NumberValueNode.ts +++ b/src/nodes/valueNodes/NumberValueNode.ts @@ -1,9 +1,9 @@ -export type NumberValueNode = { +export interface NumberValueNode { readonly kind: 'numberValueNode'; // Data. readonly number: number; -}; +} export function numberValueNode(number: number): NumberValueNode { return { kind: 'numberValueNode', number }; diff --git a/src/nodes/valueNodes/PublicKeyValueNode.ts b/src/nodes/valueNodes/PublicKeyValueNode.ts index 4a12460f2..9a62ba8d5 100644 --- a/src/nodes/valueNodes/PublicKeyValueNode.ts +++ b/src/nodes/valueNodes/PublicKeyValueNode.ts @@ -1,12 +1,12 @@ import { MainCaseString, mainCase } from '../../shared/utils'; -export type PublicKeyValueNode = { +export interface PublicKeyValueNode { readonly kind: 'publicKeyValueNode'; // Data. readonly publicKey: string; readonly identifier?: MainCaseString; -}; +} export function publicKeyValueNode( publicKey: string, diff --git a/src/nodes/valueNodes/SetValueNode.ts b/src/nodes/valueNodes/SetValueNode.ts index aaf4abed0..4fa91b3c7 100644 --- a/src/nodes/valueNodes/SetValueNode.ts +++ b/src/nodes/valueNodes/SetValueNode.ts @@ -1,12 +1,14 @@ import { ValueNode } from './ValueNode'; -export type SetValueNode = { +export interface SetValueNode { readonly kind: 'setValueNode'; // Children. - readonly items: ValueNode[]; -}; + readonly items: TItems; +} -export function setValueNode(items: ValueNode[]): SetValueNode { +export function setValueNode( + items: TItems +): SetValueNode { return { kind: 'setValueNode', items }; } diff --git a/src/nodes/valueNodes/SomeValueNode.ts b/src/nodes/valueNodes/SomeValueNode.ts index b16e68284..e9bd00d84 100644 --- a/src/nodes/valueNodes/SomeValueNode.ts +++ b/src/nodes/valueNodes/SomeValueNode.ts @@ -1,12 +1,14 @@ import { ValueNode } from './ValueNode'; -export type SomeValueNode = { +export interface SomeValueNode { readonly kind: 'someValueNode'; // Children. - readonly value: ValueNode; -}; + readonly value: TValue; +} -export function someValueNode(value: ValueNode): SomeValueNode { +export function someValueNode( + value: TValue +): SomeValueNode { return { kind: 'someValueNode', value }; } diff --git a/src/nodes/valueNodes/StringValueNode.ts b/src/nodes/valueNodes/StringValueNode.ts index 2abdea469..47ef47f27 100644 --- a/src/nodes/valueNodes/StringValueNode.ts +++ b/src/nodes/valueNodes/StringValueNode.ts @@ -1,9 +1,9 @@ -export type StringValueNode = { +export interface StringValueNode { readonly kind: 'stringValueNode'; // Data. readonly string: string; -}; +} export function stringValueNode(string: string): StringValueNode { return { kind: 'stringValueNode', string }; diff --git a/src/nodes/valueNodes/StructFieldValueNode.ts b/src/nodes/valueNodes/StructFieldValueNode.ts index 6809508fd..9fffc021a 100644 --- a/src/nodes/valueNodes/StructFieldValueNode.ts +++ b/src/nodes/valueNodes/StructFieldValueNode.ts @@ -1,19 +1,19 @@ import { MainCaseString, mainCase } from '../../shared'; import { ValueNode } from './ValueNode'; -export type StructFieldValueNode = { +export interface StructFieldValueNode { readonly kind: 'structFieldValueNode'; // Children. - readonly value: ValueNode; + readonly value: TValue; // Data. readonly name: MainCaseString; -}; +} -export function structFieldValueNode( +export function structFieldValueNode( name: string, - value: ValueNode -): StructFieldValueNode { + value: TValue +): StructFieldValueNode { return { kind: 'structFieldValueNode', name: mainCase(name), value }; } diff --git a/src/nodes/valueNodes/StructValueNode.ts b/src/nodes/valueNodes/StructValueNode.ts index 597b28f85..c1853ea8a 100644 --- a/src/nodes/valueNodes/StructValueNode.ts +++ b/src/nodes/valueNodes/StructValueNode.ts @@ -1,14 +1,16 @@ import { StructFieldValueNode } from './StructFieldValueNode'; -export type StructValueNode = { +export interface StructValueNode< + TFields extends StructFieldValueNode[] = StructFieldValueNode[], +> { readonly kind: 'structValueNode'; // Children. - readonly fields: StructFieldValueNode[]; -}; + readonly fields: TFields; +} -export function structValueNode( - fields: StructFieldValueNode[] -): StructValueNode { +export function structValueNode( + fields: TFields +): StructValueNode { return { kind: 'structValueNode', fields }; } diff --git a/src/nodes/valueNodes/TupleValueNode.ts b/src/nodes/valueNodes/TupleValueNode.ts index 3fef80473..11d5e262f 100644 --- a/src/nodes/valueNodes/TupleValueNode.ts +++ b/src/nodes/valueNodes/TupleValueNode.ts @@ -1,12 +1,14 @@ import { ValueNode } from './ValueNode'; -export type TupleValueNode = { +export interface TupleValueNode { readonly kind: 'tupleValueNode'; // Children. - readonly items: ValueNode[]; -}; + readonly items: TItems; +} -export function tupleValueNode(items: ValueNode[]): TupleValueNode { +export function tupleValueNode( + items: TItems +): TupleValueNode { return { kind: 'tupleValueNode', items }; } diff --git a/src/nodes/valueNodes/ValueNode.ts b/src/nodes/valueNodes/ValueNode.ts index dc5da21c8..ae1166802 100644 --- a/src/nodes/valueNodes/ValueNode.ts +++ b/src/nodes/valueNodes/ValueNode.ts @@ -1,4 +1,3 @@ -import { getNodeKinds } from '../../shared/utils'; import type { ArrayValueNode } from './ArrayValueNode'; import type { BooleanValueNode } from './BooleanValueNode'; import type { EnumValueNode } from './EnumValueNode'; @@ -15,45 +14,47 @@ import type { StructValueNode } from './StructValueNode'; import type { TupleValueNode } from './TupleValueNode'; // Standalone Value Node Registration. - -export const STANDALONE_VALUE_NODES = { - arrayValueNode: {} as ArrayValueNode, - booleanValueNode: {} as BooleanValueNode, - enumValueNode: {} as EnumValueNode, - mapValueNode: {} as MapValueNode, - noneValueNode: {} as NoneValueNode, - numberValueNode: {} as NumberValueNode, - setValueNode: {} as SetValueNode, - someValueNode: {} as SomeValueNode, - structValueNode: {} as StructValueNode, - tupleValueNode: {} as TupleValueNode, - publicKeyValueNode: {} as PublicKeyValueNode, - stringValueNode: {} as StringValueNode, -}; - -export const STANDALONE_VALUE_NODE_KINDS = getNodeKinds(STANDALONE_VALUE_NODES); -export type StandaloneValueNodeKind = - (typeof STANDALONE_VALUE_NODE_KINDS)[number]; export type StandaloneValueNode = - (typeof STANDALONE_VALUE_NODES)[StandaloneValueNodeKind]; + | ArrayValueNode + | BooleanValueNode + | EnumValueNode + | MapValueNode + | NoneValueNode + | NumberValueNode + | SetValueNode + | SomeValueNode + | StructValueNode + | TupleValueNode + | PublicKeyValueNode + | StringValueNode; +export const STANDALONE_VALUE_NODE_KINDS = [ + 'arrayValueNode', + 'booleanValueNode', + 'enumValueNode', + 'mapValueNode', + 'noneValueNode', + 'numberValueNode', + 'setValueNode', + 'someValueNode', + 'structValueNode', + 'tupleValueNode', + 'publicKeyValueNode', + 'stringValueNode', +] satisfies readonly StandaloneValueNode['kind'][]; +null as unknown as StandaloneValueNode['kind'] satisfies (typeof STANDALONE_VALUE_NODE_KINDS)[number]; // Value Node Registration. - -export const REGISTERED_VALUE_NODES = { - ...STANDALONE_VALUE_NODES, - - // The following are not valid standalone nodes. - mapEntryValueNode: {} as MapEntryValueNode, - structFieldValueNode: {} as StructFieldValueNode, -}; - -export const REGISTERED_VALUE_NODE_KINDS = getNodeKinds(REGISTERED_VALUE_NODES); -export type RegisteredValueNodeKind = - (typeof REGISTERED_VALUE_NODE_KINDS)[number]; export type RegisteredValueNode = - (typeof REGISTERED_VALUE_NODES)[RegisteredValueNodeKind]; + | StandaloneValueNode + | MapEntryValueNode + | StructFieldValueNode; +export const REGISTERED_VALUE_NODE_KINDS = [ + ...STANDALONE_VALUE_NODE_KINDS, + 'mapEntryValueNode', + 'structFieldValueNode', +] satisfies readonly RegisteredValueNode['kind'][]; +null as unknown as RegisteredValueNode['kind'] satisfies (typeof REGISTERED_VALUE_NODE_KINDS)[number]; // Value Node Helpers. - export type ValueNode = StandaloneValueNode; export const VALUE_NODES = STANDALONE_VALUE_NODE_KINDS; diff --git a/src/renderers/js-experimental/fragments/programAccounts.ts b/src/renderers/js-experimental/fragments/programAccounts.ts index cbbc0157f..ea990f9b7 100644 --- a/src/renderers/js-experimental/fragments/programAccounts.ts +++ b/src/renderers/js-experimental/fragments/programAccounts.ts @@ -1,4 +1,4 @@ -import { ProgramNode } from '../../../nodes'; +import { ProgramNode, resolveNestedTypeNode } from '../../../nodes'; import type { GlobalFragmentScope } from '../getRenderMapVisitor'; import { Fragment, fragment, mergeFragments } from './common'; import { getDiscriminatorConditionFragment } from './discriminatorCondition'; @@ -62,7 +62,7 @@ function getProgramAccountsIdentifierFunctionFragment( return getDiscriminatorConditionFragment({ ...scope, discriminators: account.discriminators ?? [], - struct: account.data, + struct: resolveNestedTypeNode(account.data), dataName: 'data', ifTrue: `return ${programAccountsEnum}.${variant};`, }); diff --git a/src/renderers/js-experimental/getTypeManifestVisitor.ts b/src/renderers/js-experimental/getTypeManifestVisitor.ts index 59854ac8c..fefc245df 100644 --- a/src/renderers/js-experimental/getTypeManifestVisitor.ts +++ b/src/renderers/js-experimental/getTypeManifestVisitor.ts @@ -1,9 +1,11 @@ import { + CountNode, NumberTypeNode, REGISTERED_TYPE_NODE_KINDS, - CountNode, + TypeNode, isNode, isScalarEnum, + resolveNestedTypeNode, structFieldTypeNode, structTypeNode, structTypeNodeFromInstructionArgumentNodes, @@ -145,7 +147,8 @@ export function getTypeManifestVisitor(input: { const encoderOptions: string[] = []; const decoderOptions: string[] = []; - if (enumType.size.format !== 'u8' || enumType.size.endian !== 'le') { + const enumSize = resolveNestedTypeNode(enumType.size); + if (enumSize.format !== 'u8' || enumSize.endian !== 'le') { const sizeManifest = visit(enumType.size, self); encoderImports.mergeWith(sizeManifest.encoder); decoderImports.mergeWith(sizeManifest.decoder); @@ -347,10 +350,8 @@ export function getTypeManifestVisitor(input: { const decoderOptions: string[] = []; // Prefix option. - if ( - optionType.prefix.format !== 'u8' || - optionType.prefix.endian !== 'le' - ) { + const optionPrefix = resolveNestedTypeNode(optionType.prefix); + if (optionPrefix.format !== 'u8' || optionPrefix.endian !== 'le') { const prefixManifest = visit(optionType.prefix, self); childManifest.encoder.mergeImportsWith(prefixManifest.encoder); childManifest.decoder.mergeImportsWith(prefixManifest.decoder); @@ -517,10 +518,8 @@ export function getTypeManifestVisitor(input: { let sizeEncoder = ''; let sizeDecoder = ''; - if ( - booleanType.size.format !== 'u8' || - booleanType.size.endian !== 'le' - ) { + const resolvedSize = resolveNestedTypeNode(booleanType.size); + if (resolvedSize.format !== 'u8' || resolvedSize.endian !== 'le') { const size = visit(booleanType.size, self); encoderImports.mergeWith(size.encoder); decoderImports.mergeWith(size.decoder); @@ -723,7 +722,7 @@ export function getTypeManifestVisitor(input: { }, visitSizePrefixType(sizePrefixType, { self }) { - parentSize = sizePrefixType.prefix; + parentSize = resolveNestedTypeNode(sizePrefixType.prefix); const manifest = visit(sizePrefixType.type, self); parentSize = null; return manifest; @@ -734,7 +733,7 @@ export function getTypeManifestVisitor(input: { function getArrayLikeSizeOption( count: CountNode, - visitor: Visitor + visitor: Visitor ): { encoder: Fragment; decoder: Fragment; @@ -751,7 +750,8 @@ function getArrayLikeSizeOption( decoder: fragment(`size: 'remainder'`), }; } - if (count.prefix.format === 'u32' && count.prefix.endian === 'le') { + const prefix = resolveNestedTypeNode(count.prefix); + if (prefix.format === 'u32' && prefix.endian === 'le') { return { encoder: fragment(''), decoder: fragment('') }; } const prefixManifest = visit(count.prefix, visitor); diff --git a/src/renderers/js-experimental/renderValueNodeVisitor.ts b/src/renderers/js-experimental/renderValueNodeVisitor.ts index 3daa586f5..e1221133c 100644 --- a/src/renderers/js-experimental/renderValueNodeVisitor.ts +++ b/src/renderers/js-experimental/renderValueNodeVisitor.ts @@ -1,4 +1,4 @@ -import { RegisteredValueNodeKind, isNode, isScalarEnum } from '../../nodes'; +import { RegisteredValueNode, isNode, isScalarEnum } from '../../nodes'; import { LinkableDictionary, MainCaseString } from '../../shared'; import { Visitor, visit } from '../../visitors'; import { Fragment, fragment, mergeFragments } from './fragments'; @@ -10,7 +10,7 @@ export function renderValueNodeVisitor(input: { nameApi: NameApi; linkables: LinkableDictionary; nonScalarEnums: MainCaseString[]; -}): Visitor { +}): Visitor { const { nameApi, linkables, nonScalarEnums } = input; return { visitArrayValue(node) { diff --git a/src/renderers/js/getRenderMapVisitor.ts b/src/renderers/js/getRenderMapVisitor.ts index ae695047b..6b49e2390 100644 --- a/src/renderers/js/getRenderMapVisitor.ts +++ b/src/renderers/js/getRenderMapVisitor.ts @@ -12,6 +12,7 @@ import { isNode, isNodeFilter, ProgramNode, + resolveNestedTypeNode, SizeDiscriminatorNode, structTypeNodeFromInstructionArgumentNodes, VALUE_NODES, @@ -323,9 +324,9 @@ export function getRenderMapVisitor( | (FieldDiscriminatorNode & { value: string }) | null = null; if (isNode(discriminator, 'fieldDiscriminatorNode')) { - const discriminatorField = node.data.fields.find( - (f) => f.name === discriminator.name - ); + const discriminatorField = resolveNestedTypeNode( + node.data + ).fields.find((f) => f.name === discriminator.name); const discriminatorValue = discriminatorField?.defaultValue ? visit(discriminatorField.defaultValue, valueNodeVisitor) : undefined; diff --git a/src/renderers/js/getTypeManifestVisitor.ts b/src/renderers/js/getTypeManifestVisitor.ts index 0432d316b..8831bb79c 100644 --- a/src/renderers/js/getTypeManifestVisitor.ts +++ b/src/renderers/js/getTypeManifestVisitor.ts @@ -2,10 +2,12 @@ import { ArrayTypeNode, NumberTypeNode, REGISTERED_TYPE_NODE_KINDS, + TypeNode, isInteger, isNode, isScalarEnum, isUnsignedInteger, + resolveNestedTypeNode, structFieldTypeNode, structTypeNode, structTypeNodeFromInstructionArgumentNodes, @@ -147,7 +149,8 @@ export function getTypeManifestVisitor(input: { parentName = null; const options: string[] = []; - if (enumType.size.format !== 'u8' || enumType.size.endian !== 'le') { + const enumSize = resolveNestedTypeNode(enumType.size); + if (enumSize.format !== 'u8' || enumSize.endian !== 'le') { const sizeManifest = visit(enumType.size, self); strictImports.mergeWith(sizeManifest.strictImports); looseImports.mergeWith(sizeManifest.looseImports); @@ -303,10 +306,8 @@ export function getTypeManifestVisitor(input: { const options: string[] = []; // Prefix option. - if ( - optionType.prefix.format !== 'u8' || - optionType.prefix.endian !== 'le' - ) { + const optionPrefix = resolveNestedTypeNode(optionType.prefix); + if (optionPrefix.format !== 'u8' || optionPrefix.endian !== 'le') { const prefixManifest = visit(optionType.prefix, self); childManifest.strictImports.mergeWith(prefixManifest.strictImports); childManifest.looseImports.mergeWith(prefixManifest.looseImports); @@ -466,10 +467,8 @@ export function getTypeManifestVisitor(input: { 'bool' ); let sizeSerializer = ''; - if ( - booleanType.size.format !== 'u8' || - booleanType.size.endian !== 'le' - ) { + const resolvedSize = resolveNestedTypeNode(booleanType.size); + if (resolvedSize.format !== 'u8' || resolvedSize.endian !== 'le') { const size = visit(booleanType.size, self); looseImports.mergeWith(size.looseImports); strictImports.mergeWith(size.strictImports); @@ -548,7 +547,8 @@ export function getTypeManifestVisitor(input: { visitAmountType(amountType, { self }) { const numberManifest = visit(amountType.number, self); - if (!isUnsignedInteger(amountType.number)) { + const resolvedNode = resolveNestedTypeNode(amountType.number); + if (!isUnsignedInteger(resolvedNode)) { throw new Error( `Amount wrappers can only be applied to unsigned ` + `integer types. Got type [${amountType.number.toString()}].` @@ -574,10 +574,11 @@ export function getTypeManifestVisitor(input: { visitDateTimeType(dateTimeType, { self }) { const numberManifest = visit(dateTimeType.number, self); - if (!isInteger(dateTimeType.number)) { + const dateTimeNumber = resolveNestedTypeNode(dateTimeType.number); + if (!isInteger(dateTimeNumber)) { throw new Error( `DateTime wrappers can only be applied to integer ` + - `types. Got type [${dateTimeType.number.toString()}].` + `types. Got type [${dateTimeNumber.toString()}].` ); } numberManifest.strictImports.add('umi', 'DateTime'); @@ -593,10 +594,11 @@ export function getTypeManifestVisitor(input: { visitSolAmountType(solAmountType, { self }) { const numberManifest = visit(solAmountType.number, self); - if (!isUnsignedInteger(solAmountType.number)) { + const nestedNumber = resolveNestedTypeNode(solAmountType.number); + if (!isUnsignedInteger(nestedNumber)) { throw new Error( `Amount wrappers can only be applied to unsigned ` + - `integer types. Got type [${solAmountType.number.toString()}].` + `integer types. Got type [${nestedNumber.toString()}].` ); } const idAndDecimals = `'SOL', 9`; @@ -680,7 +682,7 @@ export function getTypeManifestVisitor(input: { }, visitSizePrefixType(sizePrefixType, { self }) { - parentSize = sizePrefixType.prefix; + parentSize = resolveNestedTypeNode(sizePrefixType.prefix); const manifest = visit(sizePrefixType.type, self); parentSize = null; return manifest; @@ -715,7 +717,7 @@ function getArrayLikeSizeOption( JavaScriptTypeManifest, 'strictImports' | 'looseImports' | 'serializerImports' >, - self: Visitor + self: Visitor ): string | null { if (isNode(count, 'fixedCountNode')) return `size: ${count.value}`; if (isNode(count, 'remainderCountNode')) return `size: 'remainder'`; diff --git a/src/renderers/js/getValidatorBagVisitor.ts b/src/renderers/js/getValidatorBagVisitor.ts index 1bc557fed..5e29f4d0d 100644 --- a/src/renderers/js/getValidatorBagVisitor.ts +++ b/src/renderers/js/getValidatorBagVisitor.ts @@ -1,4 +1,4 @@ -import { Node, isDataEnum, isNode } from '../../nodes'; +import { Node, isDataEnum, isNode, resolveNestedTypeNode } from '../../nodes'; import { NodeStack, ValidatorBag, @@ -106,8 +106,8 @@ export function getValidatorBagVisitor(): Visitor { bag.mergeWith([checkExportConflicts(node, exports)]); const reservedAccountFields = new Set(['publicKey', 'header']); - const invalidFields = node.data.fields - .map((field) => field.name) + const invalidFields = resolveNestedTypeNode(node.data) + .fields.map((field) => field.name) .filter((name) => reservedAccountFields.has(name)); if (invalidFields.length > 0) { const x = invalidFields.join(', '); diff --git a/src/renderers/js/renderValueNodeVisitor.ts b/src/renderers/js/renderValueNodeVisitor.ts index b3a0fac30..f3171e237 100644 --- a/src/renderers/js/renderValueNodeVisitor.ts +++ b/src/renderers/js/renderValueNodeVisitor.ts @@ -1,4 +1,4 @@ -import { RegisteredValueNodeKind, isNode, isScalarEnum } from '../../nodes'; +import { RegisteredValueNode, isNode, isScalarEnum } from '../../nodes'; import { LinkableDictionary, MainCaseString, @@ -16,7 +16,7 @@ export function renderValueNodeVisitor(input: { imports: JavaScriptImportMap; render: string; }, - RegisteredValueNodeKind + RegisteredValueNode['kind'] > { const { linkables, nonScalarEnums } = input; return { diff --git a/src/renderers/rust/getTypeManifestVisitor.ts b/src/renderers/rust/getTypeManifestVisitor.ts index a8fb879f3..52ae5884b 100644 --- a/src/renderers/rust/getTypeManifestVisitor.ts +++ b/src/renderers/rust/getTypeManifestVisitor.ts @@ -107,11 +107,17 @@ export function getTypeManifestVisitor() { }; } - if ( - isNode(arrayType.count, 'prefixedCountNode') && - arrayType.count.prefix.endian === 'le' - ) { - switch (arrayType.count.prefix.format) { + if (isNode(arrayType.count, 'remainderCountNode')) { + childManifest.imports.add('kaigan::types::RemainderVec'); + return { + ...childManifest, + type: `RemainderVec<${childManifest.type}>`, + }; + } + + const prefix = resolveNestedTypeNode(arrayType.count.prefix); + if (prefix.endian === 'le') { + switch (prefix.format) { case 'u32': return { ...childManifest, @@ -120,28 +126,20 @@ export function getTypeManifestVisitor() { case 'u8': case 'u16': case 'u64': { - const prefix = arrayType.count.prefix.format.toUpperCase(); - childManifest.imports.add(`kaigan::types::${prefix}PrefixVec`); + const prefixFormat = prefix.format.toUpperCase(); + childManifest.imports.add( + `kaigan::types::${prefixFormat}PrefixVec` + ); return { ...childManifest, - type: `${prefix}PrefixVec<${childManifest.type}>`, + type: `${prefixFormat}PrefixVec<${childManifest.type}>`, }; } default: - throw new Error( - `Array prefix not supported: ${arrayType.count.prefix.format}` - ); + throw new Error(`Array prefix not supported: ${prefix.format}`); } } - if (isNode(arrayType.count, 'remainderCountNode')) { - childManifest.imports.add('kaigan::types::RemainderVec'); - return { - ...childManifest, - type: `RemainderVec<${childManifest.type}>`, - }; - } - // TODO: Add to the Rust validator. throw new Error('Array size not supported by Borsh'); }, @@ -246,10 +244,8 @@ export function getTypeManifestVisitor() { visitOptionType(optionType, { self }) { const childManifest = visit(optionType.item, self); - if ( - optionType.prefix.format === 'u8' && - optionType.prefix.endian === 'le' - ) { + const optionPrefix = resolveNestedTypeNode(optionType.prefix); + if (optionPrefix.format === 'u8' && optionPrefix.endian === 'le') { return { ...childManifest, type: `Option<${childManifest.type}>`, @@ -367,10 +363,8 @@ export function getTypeManifestVisitor() { }, visitBooleanType(booleanType) { - if ( - booleanType.size.format === 'u8' && - booleanType.size.endian === 'le' - ) { + const resolvedSize = resolveNestedTypeNode(booleanType.size); + if (resolvedSize.format === 'u8' && resolvedSize.endian === 'le') { return { type: 'bool', imports: new RustImportMap(), @@ -473,7 +467,7 @@ export function getTypeManifestVisitor() { }, visitSizePrefixType(sizePrefixType, { self }) { - parentSize = sizePrefixType.prefix; + parentSize = resolveNestedTypeNode(sizePrefixType.prefix); const manifest = visit(sizePrefixType.type, self); parentSize = null; return manifest; diff --git a/src/renderers/rust/renderValueNodeVisitor.ts b/src/renderers/rust/renderValueNodeVisitor.ts index 339328632..ca370caea 100644 --- a/src/renderers/rust/renderValueNodeVisitor.ts +++ b/src/renderers/rust/renderValueNodeVisitor.ts @@ -1,4 +1,4 @@ -import { RegisteredValueNodeKind, ValueNode } from '../../nodes'; +import { RegisteredValueNode, ValueNode } from '../../nodes'; import { pascalCase } from '../../shared'; import { Visitor, visit } from '../../visitors'; import { RustImportMap } from './RustImportMap'; @@ -18,7 +18,7 @@ export function renderValueNodeVisitor(useStr: boolean = false): Visitor< imports: RustImportMap; render: string; }, - RegisteredValueNodeKind + RegisteredValueNode['kind'] > { return { visitArrayValue(node) { diff --git a/src/shared/GpaField.ts b/src/shared/GpaField.ts index 01e1370a4..242eeb8b5 100644 --- a/src/shared/GpaField.ts +++ b/src/shared/GpaField.ts @@ -1,4 +1,9 @@ -import type { AccountNode, RegisteredTypeNodeKind, TypeNode } from '../nodes'; +import { + resolveNestedTypeNode, + type AccountNode, + type RegisteredTypeNode, + type TypeNode, +} from '../nodes'; import { Visitor, visit } from '../visitors'; export type GpaField = { @@ -11,11 +16,12 @@ export function getGpaFieldsFromAccount( node: AccountNode, sizeVisitor: Visitor< number | null, - RegisteredTypeNodeKind | 'definedTypeLinkNode' + RegisteredTypeNode['kind'] | 'definedTypeLinkNode' > ): GpaField[] { let offset: number | null = 0; - return node.data.fields.map((field): GpaField => { + const struct = resolveNestedTypeNode(node.data); + return struct.fields.map((field): GpaField => { const fieldOffset = offset; if (offset !== null) { const newOffset = visit(field.type, sizeVisitor); diff --git a/src/shared/utils.ts b/src/shared/utils.ts index becd5e868..e5dcc30ca 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -17,12 +17,6 @@ export type Mutable = { export type DontInfer = T extends any ? T : never; -export function getNodeKinds>( - obj: TObj -): (keyof TObj)[] { - return Object.keys(obj) as (keyof TObj)[]; -} - export function capitalize(str: string): string { if (str.length === 0) return str; return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); diff --git a/src/visitors/extendVisitor.ts b/src/visitors/extendVisitor.ts index f3ad206f9..6c6c2abba 100644 --- a/src/visitors/extendVisitor.ts +++ b/src/visitors/extendVisitor.ts @@ -1,6 +1,6 @@ import { + GetNodeFromKind, Node, - NodeDictionary, NodeKind, REGISTERED_NODE_KINDS, } from '../nodes'; @@ -27,7 +27,7 @@ export type VisitorOverrides = { [K in TNodeKind as GetVisitorFunctionName]?: VisitorOverrideFunction< TReturn, TNodeKind, - NodeDictionary[K] + GetNodeFromKind >; }; diff --git a/src/visitors/getByteSizeVisitor.ts b/src/visitors/getByteSizeVisitor.ts index db222e29a..0db8b6323 100644 --- a/src/visitors/getByteSizeVisitor.ts +++ b/src/visitors/getByteSizeVisitor.ts @@ -1,6 +1,6 @@ import { REGISTERED_TYPE_NODE_KINDS, - RegisteredTypeNodeKind, + RegisteredTypeNode, isNode, isScalarEnum, } from '../nodes'; @@ -9,7 +9,7 @@ import { mergeVisitor } from './mergeVisitor'; import { Visitor, visit } from './visitor'; export type ByteSizeVisitorKeys = - | RegisteredTypeNodeKind + | RegisteredTypeNode['kind'] | 'definedTypeLinkNode' | 'definedTypeNode' | 'accountNode' diff --git a/src/visitors/mapVisitor.ts b/src/visitors/mapVisitor.ts index a95fb19f2..7bb7207e2 100644 --- a/src/visitors/mapVisitor.ts +++ b/src/visitors/mapVisitor.ts @@ -1,4 +1,4 @@ -import { NodeDictionary, NodeKind, REGISTERED_NODE_KINDS } from '../nodes'; +import { GetNodeFromKind, NodeKind, REGISTERED_NODE_KINDS } from '../nodes'; import { GetVisitorFunctionName, Visitor, @@ -20,7 +20,7 @@ export function mapVisitor( return [ [ key, - (node: NodeDictionary[TNodeKind]) => + (node: GetNodeFromKind) => map( (visitor[key as GetVisitorFunctionName] as Function)( node diff --git a/src/visitors/setAccountDiscriminatorFromFieldVisitor.ts b/src/visitors/setAccountDiscriminatorFromFieldVisitor.ts index ba67bac20..ee0f6bdb9 100644 --- a/src/visitors/setAccountDiscriminatorFromFieldVisitor.ts +++ b/src/visitors/setAccountDiscriminatorFromFieldVisitor.ts @@ -3,8 +3,10 @@ import { accountNode, assertIsNode, fieldDiscriminatorNode, + resolveNestedTypeNode, structFieldTypeNode, structTypeNode, + transformNestedTypeNode, } from '../nodes'; import { BottomUpNodeTransformerWithSelector, @@ -24,7 +26,8 @@ export function setAccountDiscriminatorFromFieldVisitor( transform: (node) => { assertIsNode(node, 'accountNode'); - const fieldIndex = node.data.fields.findIndex( + const accountData = resolveNestedTypeNode(node.data); + const fieldIndex = accountData.fields.findIndex( (f) => f.name === field ); if (fieldIndex < 0) { @@ -33,22 +36,24 @@ export function setAccountDiscriminatorFromFieldVisitor( ); } - const fieldNode = node.data.fields[fieldIndex]; + const fieldNode = accountData.fields[fieldIndex]; return accountNode({ ...node, discriminators: [ fieldDiscriminatorNode(field, offset), ...(node.discriminators ?? []), ], - data: structTypeNode([ - ...node.data.fields.slice(0, fieldIndex), - structFieldTypeNode({ - ...fieldNode, - defaultValue: value, - defaultValueStrategy: 'omitted', - }), - ...node.data.fields.slice(fieldIndex + 1), - ]), + data: transformNestedTypeNode(node.data, () => + structTypeNode([ + ...accountData.fields.slice(0, fieldIndex), + structFieldTypeNode({ + ...fieldNode, + defaultValue: value, + defaultValueStrategy: 'omitted', + }), + ...accountData.fields.slice(fieldIndex + 1), + ]) + ), }); }, }) diff --git a/src/visitors/setAnchorDiscriminatorsVisitor.ts b/src/visitors/setAnchorDiscriminatorsVisitor.ts index 824347ee4..2832c69a4 100644 --- a/src/visitors/setAnchorDiscriminatorsVisitor.ts +++ b/src/visitors/setAnchorDiscriminatorsVisitor.ts @@ -9,6 +9,7 @@ import { numberTypeNode, structFieldTypeNode, structTypeNode, + transformNestedTypeNode, } from '../nodes'; import { getAnchorAccountDiscriminator, @@ -53,7 +54,9 @@ export function setAnchorDiscriminatorsVisitor() { fieldDiscriminatorNode('discriminator'), ...(node.discriminators ?? []), ], - data: structTypeNode([discriminatorArgument, ...node.data.fields]), + data: transformNestedTypeNode(node.data, (struct) => + structTypeNode([discriminatorArgument, ...struct.fields]) + ), }); }, diff --git a/src/visitors/singleNodeVisitor.ts b/src/visitors/singleNodeVisitor.ts index 9053b15bc..c80213ff1 100644 --- a/src/visitors/singleNodeVisitor.ts +++ b/src/visitors/singleNodeVisitor.ts @@ -1,4 +1,4 @@ -import { NodeDictionary, NodeKind, RootNode } from '../nodes'; +import { GetNodeFromKind, NodeKind, RootNode } from '../nodes'; import { GetVisitorFunctionName, Visitor, @@ -10,7 +10,7 @@ export function singleNodeVisitor< TNodeKey extends NodeKind = NodeKind, >( key: TNodeKey, - fn: (node: NodeDictionary[TNodeKey]) => TReturn + fn: (node: GetNodeFromKind) => TReturn ): Visitor { const visitor = {} as Visitor; visitor[getVisitFunctionName(key)] = fn as unknown as Visitor< diff --git a/src/visitors/tapVisitor.ts b/src/visitors/tapVisitor.ts index 4e11eb2f9..d96a53e77 100644 --- a/src/visitors/tapVisitor.ts +++ b/src/visitors/tapVisitor.ts @@ -1,4 +1,4 @@ -import { NodeDictionary, NodeKind } from '../nodes'; +import { GetNodeFromKind, NodeKind } from '../nodes'; import { GetVisitorFunctionName, Visitor, @@ -12,16 +12,16 @@ export function tapVisitor< >( visitor: TVisitor, key: TNodeKey, - tap: (node: NodeDictionary[TNodeKey]) => void + tap: (node: GetNodeFromKind) => void ): TVisitor { const newVisitor = { ...visitor }; newVisitor[getVisitFunctionName(key)] = function tappedVisitNode( this: TVisitor, - node: NodeDictionary[TNodeKey] + node: GetNodeFromKind ): TReturn { tap(node); const parentFunction = visitor[getVisitFunctionName(key)] as ( - node: NodeDictionary[TNodeKey] + node: GetNodeFromKind ) => TReturn; return parentFunction.bind(this)(node); } as TVisitor[GetVisitorFunctionName]; diff --git a/src/visitors/unwrapTupleEnumWithSingleStructVisitor.ts b/src/visitors/unwrapTupleEnumWithSingleStructVisitor.ts index 27e9302be..e21f95896 100644 --- a/src/visitors/unwrapTupleEnumWithSingleStructVisitor.ts +++ b/src/visitors/unwrapTupleEnumWithSingleStructVisitor.ts @@ -1,10 +1,13 @@ import { DefinedTypeNode, EnumTupleVariantTypeNode, + StructTypeNode, assertIsNode, enumStructVariantTypeNode, getAllDefinedTypes, isNode, + resolveNestedTypeNode, + transformNestedTypeNode, } from '../nodes'; import { MainCaseString, @@ -50,8 +53,9 @@ export function unwrapTupleEnumWithSingleStructVisitor( transform: (node, stack) => { assertIsNode(node, 'enumTupleVariantTypeNode'); if (!shouldUnwrap(node, stack)) return node; - if (node.tuple.items.length !== 1) return node; - let item = node.tuple.items[0]; + const tupleNode = resolveNestedTypeNode(node.tuple); + if (tupleNode.items.length !== 1) return node; + let item = tupleNode.items[0]; if (isNode(item, 'definedTypeLinkNode')) { if (item.importFrom) return node; const definedType = definedTypes.get(item.name); @@ -61,7 +65,11 @@ export function unwrapTupleEnumWithSingleStructVisitor( item = definedType.type; } if (!isNode(item, 'structTypeNode')) return node; - return enumStructVariantTypeNode(node.name, item); + const nestedStruct = transformNestedTypeNode( + node.tuple, + () => item as StructTypeNode + ); + return enumStructVariantTypeNode(node.name, nestedStruct); }, }, ]) diff --git a/src/visitors/updateAccountsVisitor.ts b/src/visitors/updateAccountsVisitor.ts index e06423c15..8c42c9c81 100644 --- a/src/visitors/updateAccountsVisitor.ts +++ b/src/visitors/updateAccountsVisitor.ts @@ -8,6 +8,7 @@ import { pdaLinkNode, pdaNode, programNode, + transformNestedTypeNode, } from '../nodes'; import { MainCaseString, mainCase, renameStructNode } from '../shared'; import { @@ -64,7 +65,9 @@ export function updateAccountsVisitor(map: Record) { return accountNode({ ...node, ...assignableUpdates, - data: renameStructNode(node.data, updates.data ?? {}), + data: transformNestedTypeNode(node.data, (struct) => + renameStructNode(struct, updates.data ?? {}) + ), pda: newPda, }); }, diff --git a/src/visitors/visitor.ts b/src/visitors/visitor.ts index 5d1e97ee4..4e4cda216 100644 --- a/src/visitors/visitor.ts +++ b/src/visitors/visitor.ts @@ -1,6 +1,6 @@ import { + GetNodeFromKind, Node, - NodeDictionary, NodeKind, REGISTERED_NODE_KINDS, } from '../nodes'; @@ -8,7 +8,7 @@ import { KinobiError, pascalCase } from '../shared'; export type Visitor = { [K in TNodeKind as GetVisitorFunctionName]: ( - node: NodeDictionary[K] + node: GetNodeFromKind ) => TReturn; }; diff --git a/test/nodes/typeNodes/TypeNode.test.ts b/test/nodes/typeNodes/TypeNode.test.ts new file mode 100644 index 000000000..58d0e8c4d --- /dev/null +++ b/test/nodes/typeNodes/TypeNode.test.ts @@ -0,0 +1,21 @@ +import test from 'ava'; +import { + fixedSizeTypeNode, + numberTypeNode, + resolveNestedTypeNode, + sizePrefixTypeNode, + stringTypeNode, +} from '../../../src'; + +test('it resolved nested type nodes', (t) => { + const node = sizePrefixTypeNode( + fixedSizeTypeNode(stringTypeNode(), 32), + numberTypeNode('u8') + ); + t.deepEqual(resolveNestedTypeNode(node), stringTypeNode()); +}); + +test('it returns the same instance when resolving nested types nodes', (t) => { + const node = numberTypeNode('u8'); + t.is(resolveNestedTypeNode(node), node); +}); diff --git a/test/nodes/typeNodes/TypeNode.typetest.ts b/test/nodes/typeNodes/TypeNode.typetest.ts new file mode 100644 index 000000000..2eb15a92a --- /dev/null +++ b/test/nodes/typeNodes/TypeNode.typetest.ts @@ -0,0 +1,33 @@ +import { + fixedSizeTypeNode, + numberTypeNode, + NumberTypeNode, + resolveNestedTypeNode, + ResolveNestedTypeNode, + StringTypeNode, + stringTypeNode, +} from '../../../src'; + +{ + // [ResolveNestedTypeNode]: it constraints the nested type of a node. + const stringNestedNode = fixedSizeTypeNode(stringTypeNode(), 32); + const numberNestedNode = fixedSizeTypeNode(numberTypeNode('u32'), 32); + stringNestedNode satisfies ResolveNestedTypeNode; + numberNestedNode satisfies ResolveNestedTypeNode; + // @ts-expect-error The nested type is not a number. + stringNestedNode satisfies ResolveNestedTypeNode; + // @ts-expect-error The nested type is not a string. + numberNestedNode satisfies ResolveNestedTypeNode; +} + +{ + // [resolveNestedTypeNode]: it unwraps the nested type of a node. + const stringNestedNode = fixedSizeTypeNode(stringTypeNode(), 32); + const numberNestedNode = fixedSizeTypeNode(numberTypeNode('u32'), 32); + resolveNestedTypeNode(stringNestedNode) satisfies StringTypeNode; + resolveNestedTypeNode(numberNestedNode) satisfies NumberTypeNode; + // @ts-expect-error The nested type is not a number. + resolveNestedTypeNode(stringNestedNode) satisfies NumberTypeNode; + // @ts-expect-error The nested type is not a string. + resolveNestedTypeNode(numberNestedNode) satisfies StringTypeNode; +} diff --git a/test/shared/NodeSelector.test.ts b/test/shared/NodeSelector.test.ts index a3aabcbed..e4de20d1b 100644 --- a/test/shared/NodeSelector.test.ts +++ b/test/shared/NodeSelector.test.ts @@ -247,15 +247,13 @@ const macro = test.macro({ const splTokenProgram = tree.programs[0]; const christmasProgram = tree.programs[1]; const tokenAccount = splTokenProgram.accounts[0]; -const tokenDelegatedAmountOption = tokenAccount.data.fields[3] - .type as OptionTypeNode; +const tokenDelegatedAmountOption = tokenAccount.data.fields[3].type; const mintTokenInstruction = splTokenProgram.instructions[0]; const giftAccount = christmasProgram.accounts[0]; const openGiftInstruction = christmasProgram.instructions[0]; const wrappingPaper = christmasProgram.definedTypes[0]; -const wrappingPaperEnum = wrappingPaper.type as EnumTypeNode; -const wrappingPaperEnumGold = wrappingPaperEnum - .variants[2] as EnumStructVariantTypeNode; +const wrappingPaperEnum = wrappingPaper.type; +const wrappingPaperEnumGold = wrappingPaperEnum.variants[2]; // Select programs. test(macro, '[programNode]', [splTokenProgram, christmasProgram]); @@ -325,7 +323,7 @@ test(macro, '[structFieldTypeNode].*', [ tokenDelegatedAmountOption.item, giftAccount.data.fields[0].type, giftAccount.data.fields[1].type, - (giftAccount.data.fields[1].type as BooleanTypeNode).size, + giftAccount.data.fields[1].type.size, giftAccount.data.fields[2].type, giftAccount.data.fields[3].type, wrappingPaperEnumGold.struct.fields[0].type, @@ -333,7 +331,7 @@ test(macro, '[structFieldTypeNode].*', [ test(macro, '[structFieldTypeNode].*.*', [ tokenDelegatedAmountOption.prefix, tokenDelegatedAmountOption.item, - (giftAccount.data.fields[1].type as BooleanTypeNode).size, + giftAccount.data.fields[1].type.size, ]); // Select multiple node kinds. diff --git a/test/visitors/setStructDefaultValuesVisitor.test.ts b/test/visitors/setStructDefaultValuesVisitor.test.ts index fe1d528bd..566e2f95a 100644 --- a/test/visitors/setStructDefaultValuesVisitor.test.ts +++ b/test/visitors/setStructDefaultValuesVisitor.test.ts @@ -10,6 +10,7 @@ import { numberValueNode, optionTypeNode, publicKeyTypeNode, + resolveNestedTypeNode, setStructDefaultValuesVisitor, structFieldTypeNode, structTypeNode, @@ -81,10 +82,11 @@ test('it adds new default values with custom strategies to struct fields', (t) = // Then we expect the following tree changes. assertIsNode(result, 'accountNode'); - t.deepEqual(result.data.fields[0].defaultValue, numberValueNode(42)); - t.is(result.data.fields[0].defaultValueStrategy, 'omitted'); - t.deepEqual(result.data.fields[1].defaultValue, noneValueNode()); - t.is(result.data.fields[1].defaultValueStrategy, 'optional'); + const data = resolveNestedTypeNode(result.data); + t.deepEqual(data.fields[0].defaultValue, numberValueNode(42)); + t.is(data.fields[0].defaultValueStrategy, 'omitted'); + t.deepEqual(data.fields[1].defaultValue, noneValueNode()); + t.is(data.fields[1].defaultValueStrategy, 'optional'); }); test('it adds new default values to instruction arguments', (t) => { diff --git a/test/visitors/updateAccountsVisitor.test.ts b/test/visitors/updateAccountsVisitor.test.ts index 9a5883c84..0d22dc3d5 100644 --- a/test/visitors/updateAccountsVisitor.test.ts +++ b/test/visitors/updateAccountsVisitor.test.ts @@ -8,6 +8,7 @@ import { pdaLinkNode, pdaNode, programNode, + resolveNestedTypeNode, rootNode, structFieldTypeNode, structTypeNode, @@ -94,7 +95,8 @@ test("it renames the fields of an account's data", (t) => { // Then we expect the following tree changes. assertIsNode(result, 'accountNode'); - t.is(result.data.fields[0].name, 'myNewData' as MainCaseString); + const data = resolveNestedTypeNode(result.data); + t.is(data.fields[0].name, 'myNewData' as MainCaseString); }); test('it updates the name of associated PDA nodes', (t) => {