Skip to content

Commit

Permalink
Add Contextual Value Nodes and use instead of InstructionDefaults (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva authored Jan 1, 2024
1 parent c517404 commit b3ec72c
Show file tree
Hide file tree
Showing 122 changed files with 1,039 additions and 1,318 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-ducks-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@metaplex-foundation/kinobi': minor
---

Add Contextual Value Nodes and use instead of InstructionDefaults
8 changes: 7 additions & 1 deletion src/nodes/AccountNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ import { createTypeNodeFromIdl } from './typeNodes/TypeNode';

export type AccountNode = {
readonly kind: 'accountNode';

// Children.
readonly data: AccountDataNode;
readonly pda?: PdaLinkNode;

// Children to-be.
readonly discriminator?: AccountDiscriminator;

// Data.
readonly name: MainCaseString;
readonly idlName: string;
readonly docs: string[];
readonly internal: boolean;
readonly size?: number | null;
readonly discriminator?: AccountDiscriminator;
};

export type AccountNodeInput = Omit<Partial<AccountNode>, 'kind' | 'name'> & {
Expand Down
6 changes: 5 additions & 1 deletion src/nodes/DefinedTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { TypeNode, createTypeNodeFromIdl } from './typeNodes/TypeNode';

export type DefinedTypeNode = {
readonly kind: 'definedTypeNode';
readonly name: MainCaseString;

// Children.
readonly data: TypeNode;

// Data.
readonly name: MainCaseString;
readonly idlName: string;
readonly docs: string[];
readonly internal: boolean;
Expand Down
14 changes: 7 additions & 7 deletions src/nodes/InstructionAccountNode.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { IdlInstructionAccount } from '../idl';
import {
InstructionAccountDefault,
MainCaseString,
PartialExcept,
mainCase,
} from '../shared';
import { MainCaseString, PartialExcept, mainCase } from '../shared';
import { InstructionInputValueNode } from './contextualValueNodes';

export type InstructionAccountNode = {
readonly kind: 'instructionAccountNode';

// Children.
readonly defaultsTo?: InstructionInputValueNode;

// Data.
readonly name: MainCaseString;
readonly isWritable: boolean;
readonly isSigner: boolean | 'either';
readonly isOptional: boolean;
readonly docs: string[];
readonly defaultsTo?: InstructionAccountDefault;
};

export type InstructionAccountNodeInput = Omit<
Expand Down
18 changes: 12 additions & 6 deletions src/nodes/InstructionNode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { IdlInstruction } from '../idl';
import {
BytesCreatedOnChain,
InstructionArgDefault,
InvalidKinobiTreeError,
MainCaseString,
PartialExcept,
Expand All @@ -23,6 +22,7 @@ import {
import { isNode } from './Node';
import { ProgramNode } from './ProgramNode';
import { RootNode } from './RootNode';
import { InstructionInputValueNode } from './contextualValueNodes';
import { structFieldTypeNode } from './typeNodes/StructFieldTypeNode';
import {
structTypeNode,
Expand All @@ -33,17 +33,23 @@ import { numberValueNode } from './valueNodes';

export type InstructionNode = {
readonly kind: 'instructionNode';
readonly name: MainCaseString;

// Children.
readonly accounts: InstructionAccountNode[];
readonly dataArgs: InstructionDataArgsNode;
readonly extraArgs: InstructionExtraArgsNode;
readonly subInstructions: InstructionNode[];
readonly argDefaults: Record<MainCaseString, InstructionInputValueNode>;

// Children to-be.
readonly bytesCreatedOnChain?: BytesCreatedOnChain;
readonly remainingAccounts?: RemainingAccounts;

// Data.
readonly name: MainCaseString;
readonly idlName: string;
readonly docs: string[];
readonly internal: boolean;
readonly bytesCreatedOnChain?: BytesCreatedOnChain;
readonly remainingAccounts?: RemainingAccounts;
readonly argDefaults: Record<MainCaseString, InstructionArgDefault>;
readonly optionalAccountStrategy: 'omitted' | 'programId';
};

Expand All @@ -52,7 +58,7 @@ export type InstructionNodeInput = Omit<
'kind' | 'name' | 'argDefaults'
> & {
readonly name: string;
readonly argDefaults?: Record<string, InstructionArgDefault>;
readonly argDefaults?: Record<string, InstructionInputValueNode>;
};

export function instructionNode(input: InstructionNodeInput): InstructionNode {
Expand Down
26 changes: 15 additions & 11 deletions src/nodes/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type { InstructionNode } from './InstructionNode';
import type { PdaNode } from './PdaNode';
import type { ProgramNode } from './ProgramNode';
import type { RootNode } from './RootNode';
import { REGISTERED_LINK_NODES } from './linkNodes';
import { REGISTERED_PDA_SEED_NODES } from './pdaSeedNodes';
import { REGISTERED_SIZE_NODES } from './sizeNodes';
import { REGISTERED_TYPE_NODES } from './typeNodes';
import { REGISTERED_VALUE_NODES } from './valueNodes';
import { REGISTERED_CONTEXTUAL_VALUE_NODES } from './contextualValueNodes/ContextualValueNode';
import { REGISTERED_LINK_NODES } from './linkNodes/LinkNode';
import { REGISTERED_PDA_SEED_NODES } from './pdaSeedNodes/PdaSeedNode';
import { REGISTERED_SIZE_NODES } from './sizeNodes/SizeNode';
import { REGISTERED_TYPE_NODES } from './typeNodes/TypeNode';
import { REGISTERED_VALUE_NODES } from './valueNodes/ValueNode';

// Node Registration.

Expand All @@ -31,6 +32,7 @@ const REGISTERED_NODES = {
definedTypeNode: {} as DefinedTypeNode,

// Groups.
...REGISTERED_CONTEXTUAL_VALUE_NODES,
...REGISTERED_LINK_NODES,
...REGISTERED_PDA_SEED_NODES,
...REGISTERED_SIZE_NODES,
Expand All @@ -49,15 +51,15 @@ export type RegisteredNodes = typeof REGISTERED_NODES;
export type Node = RegisteredNodes[keyof RegisteredNodes];

export function isNode<TKeys extends keyof RegisteredNodes>(
node: Node | null,
node: Node | null | undefined,
key: TKeys | TKeys[]
): node is RegisteredNodes[TKeys] {
const keys = Array.isArray(key) ? key : [key];
return !!node && (keys as (keyof RegisteredNodes)[]).includes(node.kind);
}

export function assertIsNode<TKeys extends keyof RegisteredNodes>(
node: Node | null,
node: Node | null | undefined,
key: TKeys | TKeys[]
): asserts node is RegisteredNodes[TKeys] {
const keys = Array.isArray(key) ? key : [key];
Expand All @@ -70,13 +72,13 @@ export function assertIsNode<TKeys extends keyof RegisteredNodes>(

export function isNodeFilter<TKeys extends keyof RegisteredNodes>(
key: TKeys | TKeys[]
): (node: Node | null) => node is RegisteredNodes[TKeys] {
): (node: Node | null | undefined) => node is RegisteredNodes[TKeys] {
return (node): node is RegisteredNodes[TKeys] => isNode(node, key);
}

export function assertIsNodeFilter<TKeys extends keyof RegisteredNodes>(
key: TKeys | TKeys[]
): (node: Node | null) => node is RegisteredNodes[TKeys] {
): (node: Node | null | undefined) => node is RegisteredNodes[TKeys] {
return (node): node is RegisteredNodes[TKeys] => {
assertIsNode(node, key);
return true;
Expand All @@ -85,9 +87,11 @@ export function assertIsNodeFilter<TKeys extends keyof RegisteredNodes>(

export function removeNullAndAssertIsNodeFilter<
TKeys extends keyof RegisteredNodes
>(key: TKeys | TKeys[]): (node: Node | null) => node is RegisteredNodes[TKeys] {
>(
key: TKeys | TKeys[]
): (node: Node | null | undefined) => node is RegisteredNodes[TKeys] {
return (node): node is RegisteredNodes[TKeys] => {
if (node) assertIsNode(node, key);
return node !== null;
return node != null;
};
}
10 changes: 10 additions & 0 deletions src/nodes/contextualValueNodes/AccountBumpValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MainCaseString, mainCase } from '../../shared';

export type AccountBumpValueNode = {
readonly kind: 'accountBumpValueNode';
readonly name: MainCaseString;
};

export function accountBumpValueNode(name: string): AccountBumpValueNode {
return { kind: 'accountBumpValueNode', name: mainCase(name) };
}
10 changes: 10 additions & 0 deletions src/nodes/contextualValueNodes/AccountValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MainCaseString, mainCase } from '../../shared';

export type AccountValueNode = {
readonly kind: 'accountValueNode';
readonly name: MainCaseString;
};

export function accountValueNode(name: string): AccountValueNode {
return { kind: 'accountValueNode', name: mainCase(name) };
}
10 changes: 10 additions & 0 deletions src/nodes/contextualValueNodes/ArgumentValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MainCaseString, mainCase } from '../../shared';

export type ArgumentValueNode = {
readonly kind: 'argumentValueNode';
readonly name: MainCaseString;
};

export function argumentValueNode(name: string): ArgumentValueNode {
return { kind: 'argumentValueNode', name: mainCase(name) };
}
35 changes: 35 additions & 0 deletions src/nodes/contextualValueNodes/ConditionalValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ValueNode } from '../valueNodes';
import { AccountValueNode } from './AccountValueNode';
import { ArgumentValueNode } from './ArgumentValueNode';
import {
INSTRUCTION_INPUT_VALUE_NODE,
InstructionInputValueNode,
} from './ContextualValueNode';
import { ResolverValueNode } from './ResolverValueNode';

export type ConditionalValueBranch = InstructionInputValueNode;

export const CONDITIONAL_VALUE_BRANCH_NODES = INSTRUCTION_INPUT_VALUE_NODE;

export type ConditionalValueNode = {
readonly kind: 'conditionalValueNode';
readonly condition: ResolverValueNode | AccountValueNode | ArgumentValueNode;
readonly value?: ValueNode;
readonly ifTrue?: ConditionalValueBranch;
readonly ifFalse?: ConditionalValueBranch;
};

export function conditionalValueNode(input: {
condition: ConditionalValueNode['condition'];
value?: ConditionalValueNode['value'];
ifTrue?: ConditionalValueNode['ifTrue'];
ifFalse?: ConditionalValueNode['ifFalse'];
}): ConditionalValueNode {
return {
kind: 'conditionalValueNode',
condition: input.condition,
value: input.value,
ifTrue: input.ifTrue,
ifFalse: input.ifFalse,
};
}
56 changes: 56 additions & 0 deletions src/nodes/contextualValueNodes/ContextualValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Mutable } from '../../shared';
import type { ProgramLinkNode } from '../linkNodes/ProgramLinkNode';
import { VALUE_NODES, ValueNode } from '../valueNodes/ValueNode';
import type { AccountBumpValueNode } from './AccountBumpValueNode';
import type { AccountValueNode } from './AccountValueNode';
import type { ArgumentValueNode } from './ArgumentValueNode';
import type { ConditionalValueNode } from './ConditionalValueNode';
import type { IdentityValueNode } from './IdentityValueNode';
import type { PayerValueNode } from './PayerValueNode';
import type { PdaValueNode } from './PdaValueNode';
import type { ProgramIdValueNode } from './ProgramIdValueNode';
import type { ResolverValueNode } from './ResolverValueNode';

// Node Group Registration.

export const REGISTERED_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 REGISTERED_CONTEXTUAL_VALUE_NODE_KEYS = Object.keys(
REGISTERED_CONTEXTUAL_VALUE_NODES
) as (keyof typeof REGISTERED_CONTEXTUAL_VALUE_NODES)[];

export type RegisteredContextualValueNodes =
typeof REGISTERED_CONTEXTUAL_VALUE_NODES;

// Node Group Helpers.

export type ContextualValueNode =
RegisteredContextualValueNodes[keyof RegisteredContextualValueNodes];

export const CONTEXTUAL_VALUE_NODES = REGISTERED_CONTEXTUAL_VALUE_NODE_KEYS;

export type InstructionInputValueNode =
| ValueNode
| ContextualValueNode
| ProgramLinkNode;

const INSTRUCTION_INPUT_VALUE_NODE_INTERNAL = [
...VALUE_NODES,
...CONTEXTUAL_VALUE_NODES,
'programLinkNode',
] as const;

export const INSTRUCTION_INPUT_VALUE_NODE =
INSTRUCTION_INPUT_VALUE_NODE_INTERNAL as Mutable<
typeof INSTRUCTION_INPUT_VALUE_NODE_INTERNAL
>;
7 changes: 7 additions & 0 deletions src/nodes/contextualValueNodes/IdentityValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type IdentityValueNode = {
readonly kind: 'identityValueNode';
};

export function identityValueNode(): IdentityValueNode {
return { kind: 'identityValueNode' };
}
7 changes: 7 additions & 0 deletions src/nodes/contextualValueNodes/PayerValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type PayerValueNode = {
readonly kind: 'payerValueNode';
};

export function payerValueNode(): PayerValueNode {
return { kind: 'payerValueNode' };
}
44 changes: 44 additions & 0 deletions src/nodes/contextualValueNodes/PdaValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { MainCaseString, mainCase } from '../../shared';
import { isNode } from '../Node';
import { PdaNode } from '../PdaNode';
import { PdaLinkNode, pdaLinkNode } from '../linkNodes';
import { ValueNode } from '../valueNodes';
import { AccountValueNode, accountValueNode } from './AccountValueNode';
import { ArgumentValueNode, argumentValueNode } from './ArgumentValueNode';

export type PdaValueNode = {
readonly kind: 'pdaValueNode';
readonly pda: PdaLinkNode;
readonly seeds: Record<
MainCaseString,
ValueNode | AccountValueNode | ArgumentValueNode
>;
};

export function pdaValueNode(
pda: PdaLinkNode | string,
seeds: Record<string, ValueNode | AccountValueNode | ArgumentValueNode> = {}
): PdaValueNode {
return {
kind: 'pdaValueNode',
pda: typeof pda === 'string' ? pdaLinkNode(pda) : pda,
seeds: Object.entries(seeds).reduce((acc, [name, seedValue]) => {
acc[mainCase(name)] = seedValue;
return acc;
}, {} as PdaValueNode['seeds']),
};
}

export function getDefaultSeedValuesFromPda(
node: PdaNode
): PdaValueNode['seeds'] {
return node.seeds.reduce((acc, seed) => {
if (!isNode(seed, 'variablePdaSeedNode')) return acc;
if (isNode(seed.type, 'publicKeyTypeNode')) {
acc[seed.name] = accountValueNode(seed.name);
} else {
acc[seed.name] = argumentValueNode(seed.name);
}
return acc;
}, {} as PdaValueNode['seeds']);
}
7 changes: 7 additions & 0 deletions src/nodes/contextualValueNodes/ProgramIdValueNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type ProgramIdValueNode = {
readonly kind: 'programIdValueNode';
};

export function programIdValueNode(): ProgramIdValueNode {
return { kind: 'programIdValueNode' };
}
Loading

0 comments on commit b3ec72c

Please sign in to comment.