github codama-idl/codama @codama/visitors-core@2.0.0-rc.0

Major Changes

  • #1262 e1cce0d Thanks @lorisleiva! - Remove the deprecated node kind aliases, the ProgramVersion type and the duplicate node path formatters. Each has a canonical replacement that was already exported.

    BREAKING CHANGES

    Node kind aliases removed. The legacy *_NODES aliases of @codama/nodes are removed in favour of the *_NODE_KINDS constants generated from the spec.

    - import { CONTEXTUAL_VALUE_NODES, COUNT_NODES, DISCRIMINATOR_NODES, INSTRUCTION_INPUT_VALUE_NODES } from '@codama/nodes';
    - import { LINK_NODES, PDA_SEED_NODES, TYPE_NODES, VALUE_NODES } from '@codama/nodes';
    + import { CONTEXTUAL_VALUE_NODE_KINDS, COUNT_NODE_KINDS, DISCRIMINATOR_NODE_KINDS, INSTRUCTION_INPUT_VALUE_NODE_KINDS } from '@codama/nodes';
    + import { LINK_NODE_KINDS, PDA_SEED_NODE_KINDS, TYPE_NODE_KINDS, VALUE_NODE_KINDS } from '@codama/nodes';

    ProgramVersion removed. Use the Version type instead.

    - import type { ProgramVersion } from '@codama/node-types';
    + import type { Version } from '@codama/node-types';

    Node path formatters removed. nodePathToString and nodePathToStringArray are removed from @codama/visitors-core. Use getNodePathString from @codama/errors, which renders paths the way error messages do, or NodeStack#toString for the current path of a stack.

    - import { nodePathToString, nodePathToStringArray } from '@codama/visitors-core';
    + import { getNodePathString } from '@codama/errors';
    
    - nodePathToString(path);
    + getNodePathString(path);
    - nodePathToStringArray(path);
    + path.map(node => getNodePathString([node]));
  • #1157 64d52eb Thanks @codama-releases! - Release Codama v2, supporting IDLs of the v2 standard. Upgrade v1 IDLs with the codama upgrade command or the @codama/upgrade package.

  • #1264 909960f Thanks @lorisleiva! - Trim public exports that were only meant for internal use, so the public API only exposes what consumers need.

    BREAKING CHANGES

    @codama/errors no longer exports safeCaptureStackTrace. It was unused, even by CodamaError, and is also gone from codama, which re-exports it. Call Error.captureStackTrace directly if you need it.

    @codama/fragments/javascript no longer exports preferIncoming. Use mergeImportMaps or addToImportMap, which apply the same conflict-resolution rule.

    @codama/dynamic-address-resolution no longer exports OPTIONAL_NODE_KINDS. Check the type node kind directly, e.g. isNode(type, ['optionTypeNode', 'remainderOptionTypeNode', 'zeroableOptionTypeNode']).

    @codama/nodes-from-anchor only exports its root and program converters. The per-node converters, their options types and the generics helpers are now internal. Convert whole IDLs with rootNodeFromAnchor (or rootNodeFromAnchorV00, rootNodeFromAnchorV01, programNodeFromAnchorV00 and programNodeFromAnchorV01) and pick the nodes you need from the result. The removed exports are:

    • accountNodeFromAnchorV00, arrayTypeNodeFromAnchorV00, constantNodeFromAnchorV00, definedTypeNodeFromAnchorV00, enumTypeNodeFromAnchorV00, enumVariantTypeNodeFromAnchorV00, errorNodeFromAnchorV00, eventNodeFromAnchorV00, instructionAccountNodeFromAnchorV00, instructionAccountNodesFromAnchorV00, instructionNodeFromAnchorV00, mapTypeNodeFromAnchorV00, optionTypeNodeFromAnchorV00, pdaNodeFromAnchorV00, pdaSeedTypeNodeFromAnchorV00, setTypeNodeFromAnchorV00, structFieldTypeNodeFromAnchorV00, structTypeNodeFromAnchorV00, tupleTypeNodeFromAnchorV00, typeNodeFromAnchorV00 and InstructionAccountNodeFromAnchorV00Options.
    • accountNodeFromAnchorV01, arrayTypeNodeFromAnchorV01, constantNodeFromAnchorV01, definedTypeNodeFromAnchorV01, enumTypeNodeFromAnchorV01, enumVariantTypeNodeFromAnchorV01, errorNodeFromAnchorV01, eventNodeFromAnchorV01, instructionAccountNodeFromAnchorV01, instructionAccountNodesFromAnchorV01, instructionNodeFromAnchorV01, optionTypeNodeFromAnchorV01, pdaSeedNodeFromAnchorV01, structFieldTypeNodeFromAnchorV01, structTypeNodeFromAnchorV01, tupleTypeNodeFromAnchorV01, typeNodeFromAnchorV01, unwrapGenericTypeFromAnchorV01, extractGenerics, GenericsV01, InstructionAccountNodeFromAnchorV01Options and PdaSeedNodeFromAnchorV01Options.
    • extractPdasFromProgram: use extractPdasVisitor instead.
    - import { accountNodeFromAnchorV01 } from '@codama/nodes-from-anchor';
    - const account = accountNodeFromAnchorV01(idl.accounts[0], idl.types, generics);
    + import { rootNodeFromAnchor } from '@codama/nodes-from-anchor';
    + const account = rootNodeFromAnchor(idl).program.accounts?.[0];

    @codama/visitors-core no longer exports LINKABLE_NODES, getDependencies and assertNodePathHasProgram. These are also gone from @codama/visitors and codama, which re-export it. Use findProgramNodeFromPath to check that a path has a program, and getResolvedInstructionInputsVisitor to resolve instruction input dependencies.

  • #1261 bb09077 Thanks @lorisleiva! - Carry node paths in the remaining error contexts. Contexts that still held nodes or account names now hold node paths instead, e.g. the cycle of a circular dependency lists the path of each account and data field it goes through. Production-encoded contexts encode lists of node paths as lists of node selectors. getInstructionDataFields now returns the fieldPath of each field, and mapVisitor gives the visited node to its map function.

    BREAKING CHANGES

    Nodes and account names replaced by node paths in error contexts. The following contexts changed, completing the move to node paths.

      if (isCodamaError(e, CODAMA_ERROR__DYNAMIC_CLIENT__CIRCULAR_ACCOUNT_DEPENDENCY)) {
    -     const { chain } = e.context; // "a -> b -> a"
    +     const { cycle, formattedCycle, path } = e.context; // formattedCycle: "a -> b -> a"
    +     const account = cycle[0].at(-1); // instructionAccountNode a
      }
      if (isCodamaError(e, CODAMA_ERROR__DYNAMIC_CLIENT__UNEXPECTED_ADDRESS_TYPE)) {
    -     const { accountName } = e.context;
    +     const account = e.context.path.at(-1); // e.g. instructionAccountNode
      }
    Code Removed Added
    VISITORS__CYCLIC_DEPENDENCY_DETECTED_WHEN_RESOLVING_INSTRUCTION_DEFAULT_VALUES (1200002) cycle holds the path of each account and data field instead of the nodes themselves
    VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY (1200004) dependency use dependencyKind and dependencyName
    VISITORS__INVALID_NUMBER_WRAPPER (1200006) path of the number to wrap (empty when the wrapper is invalid on its own)
    VISITORS__FAILED_TO_VALIDATE_NODE (1200008) path of the validated node
    DYNAMIC_CLIENT__INSTRUCTION_NOT_FOUND (2500000) path of the program
    DYNAMIC_CLIENT__PDA_NOT_FOUND (2500001) path of the program
    DYNAMIC_CLIENT__UNEXPECTED_ADDRESS_TYPE (2500005) accountName path of the account, remaining accounts, PDA or data field, and the index of the remaining account at fault
    DYNAMIC_CLIENT__CIRCULAR_ACCOUNT_DEPENDENCY (2500008) chain path of the instruction, cycle, formattedCycle

    toAddressOrThrow takes a node path. The second argument of toAddressOrThrow from @codama/dynamic-address-resolution is now the path of the node the value should be the address of, rather than an account name.

    - toAddressOrThrow(value, 'mint');
    + toAddressOrThrow(value, [root, program, instruction, mint]);

    It also takes an optional index for values given in a list, e.g. toAddressOrThrow(value, [root, program, instruction, signers], 1), which the error carries in its context (it is not rendered in the message).

    Messages changed accordingly. The messages of these errors now render their path, e.g. Circular dependency detected: [a -> b -> a]. becomes Circular dependency detected when resolving the accounts and data of instruction [programNode]token.[instructionNode]transfer. Got the following dependency cycle [a -> b -> a]..

  • #1251 277eae1 Thanks @lorisleiva! - Stop exporting the test-only NODE_TEST_PATHS constant.

    BREAKING CHANGES

    NODE_TEST_PATHS removed. This test-only constant, mapping each node kind to its test fixture path, is no longer exported from @codama/visitors-core.

    - import { NODE_TEST_PATHS } from '@codama/visitors-core';
  • #1251 277eae1 Thanks @lorisleiva! - Clear the LinkableDictionary before each top-level visit of recordLinkablesOnFirstVisitVisitor, so a reused visitor no longer resolves links against previously visited trees. Also add a LinkableDictionary.clear() method.

    BREAKING CHANGES

    recordLinkablesOnFirstVisitVisitor clears its dictionary. recordLinkablesOnFirstVisitVisitor clears the dictionary before each top-level visit, so linkables recorded into it beforehand are discarded; record extra linkables inside the visitor instead.

  • #1260 3ad402a Thanks @lorisleiva! - Require Node.js 22.12.0 or later.

    BREAKING CHANGES

    Node.js 20 is no longer supported. Every package now declares "engines": { "node": ">=22.12.0" }, the first Node.js 22 release that supports require(esm) by default. Node.js 20 reached its end of life in April 2026, and some dependencies (e.g. commander) already require Node.js 22.12.0.

  • #1248 7f8beda Thanks @lorisleiva! - Add ProvidedScope to resolve injectedValueNodes from the provides of enclosing instructions, add getInstructionDataFields, and rework getResolvedInstructionInputsVisitor around instruction data fields and injected values.

    • ProvidedScope holds the lexical frames of providedNodes visible at a point of the tree, one per instructionNode with provides, the innermost frame winning. scope.resolve(node, { kinds }) replaces every injection within node (itself included), using fallbacks when nothing is provided, and returns undefined when an injection resolves to nothing. scope.resolveOrThrow(node, { kinds }) throws CODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDED instead. Resolved values of the wrong kind throw CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE. scope.get(key), push, pop and clone are also available.
    • recordProvidedScopeVisitor(visitor, scope) keeps a ProvidedScope in sync with a traversal, and getProvidedScopeFromPath(path) builds the scope of the instructions along a path.
    • getInstructionDataFields(instructionPath, linkables) lists every struct field addressable within an instruction's data, with its full path (InstructionDataField), e.g. amount, config and config.fee, following defined type links.

    BREAKING CHANGES

    getResolvedInstructionInputsVisitor takes a LinkableDictionary. It is now its first argument, used to follow defined type links in the instruction's data. Its options gain stack (the ancestry of the visited instruction, which must contain its programNode for linked data to be followed) and scope (the ProvidedScope of enclosing instructions), and includeDataArgumentValueNodes is renamed includeDataValueNodes.

    - visit(instruction, getResolvedInstructionInputsVisitor({ includeDataArgumentValueNodes: true }));
    + visit(instruction, getResolvedInstructionInputsVisitor(linkables, { includeDataValueNodes: true, stack }));

    Resolved inputs wrap their node. ResolvedInstructionAccount no longer extends InstructionAccountNode: the account is under node, alongside dependsOn, isPda, resolvedIsOptional, resolvedIsSigner and a new resolvedDefaultValue with injections resolved. ResolvedInstructionArgument is replaced by ResolvedInstructionDataField (node, path, dependsOn, resolvedDefaultValue), for the fields of the instruction's data, nested fields included, addressed by their path (e.g. config.bump).

    - resolvedInputs.map(input => input.name);
    + resolvedInputs.map(input => (isNode(input.node, 'instructionAccountNode') ? input.node.identifier : input.path));

    Instruction dependencies are data values. InstructionDependency is now exported and is AccountValueNode | DataValueNode (previously AccountValueNode | ArgumentValueNode), and getInstructionDependencies(input) is replaced by getDependencies(defaultValue), which takes the default value to inspect rather than an instruction or input.

  • #1248 7f8beda Thanks @lorisleiva! - Add resolveTypePath to follow path expressions such as config.fees[0] through types, and isValueNodeOfType to check a value node against a type. NodePath no longer distributes over node unions, nodePathToString renders paths like error messages do, and node selectors match identifiers exactly.

    New helpers:

    • resolveTypePath(source, path, linkables, options?) resolves a PathString against the type at the end of source and returns the full path of the node it points to: field segments select struct fields, index segments select tuple items or the item of an array or set, and defined type links are followed to their definitions. With { followAccountLinks: true } (ResolveTypePathOptions), a .data segment on a public key carrying an accountLink continues into the linked account's data, e.g. mint.data.decimals. It throws CODAMA_ERROR__CANNOT_RESOLVE_PATH when a segment cannot be followed.
    • parsePath(path) splits a path expression into PathSegments ({ kind: 'field', identifier } or { kind: 'index', index }).
    • TypePathNode (a registered type node or a definedTypeLinkNode) and TypePath, a path ending at one.
    • isValueNodeOfType(value, typePath, linkables) tells whether a concrete value node structurally fits the type at the end of typePath, following defined type links, e.g. noneValueNode() for an optionTypeNode.
    • assertNodePathHasProgram(path) throws CODAMA_ERROR__NODE_PATH_PROGRAM_MISSING when a path has no programNode to resolve links from.

    BREAKING CHANGES

    NodePath is no longer distributive. NodePath<A | B> is now one path ending at A | B rather than the union NodePath<A> | NodePath<B>, so paths built from nodes only known as a union, e.g. [...stack.getPath(), node], type-check. Parameters typed as NodePath<A> | NodePath<B> no longer accept such paths and should use NodePath<A | B>.

    - function visitLink(path: NodePath<AccountLinkNode> | NodePath<PdaLinkNode>) {}
    + function visitLink(path: NodePath<AccountLinkNode | PdaLinkNode>) {}

    nodePathToString output changed. It now renders paths like getNodePathString from @codama/errors: node selectors joined by dots, leaving out a leading rootNode. nodePathToStringArray renders each node with its identifier.

    - nodePathToString(path); // '[rootNode] > [programNode]token > [accountNode]mint'
    + nodePathToString(path); // '[programNode]token.[accountNode]mint'

    Node selectors match identifiers exactly. The name part of a NodeSelectorPath, e.g. mint_authority in [instructionAccountNode]mint_authority, is no longer converted to camelCase before being compared, and is matched against the node's identifier.

  • #1158 08d72e7 Thanks @lorisleiva! - Regenerate the node types, constructors and core visitors from the Codama v2 spec (@codama/spec@2).

    The node model changes substantially from v1:

    • name → identifier. Every named node (programNode, accountNode, instructionNode, links, …) now carries identifier instead of name, and identifiers preserve their casing — v2 no longer mandates camelCase (transfer_tokens and transferTokens are both valid; uniqueness is resolved by case-folding).
    • Numeric system rework. numberTypeNode/numberValueNode split into integerTypeNode/floatTypeNode and integerValueNode/floatValueNode (numeric values are string-encoded to stay lossless through JSON). amountTypeNode/solAmountTypeNode become fixedPointTypeNode; new durationTypeNode; dateTimeTypeNode/durationTypeNode carry ticksPerSecond.
    • Flat transforms. The wrapper type nodes (fixedSizeTypeNode, sizePrefixTypeNode, pre/postOffsetTypeNode, sentinelTypeNode, hiddenPrefix/SuffixTypeNode) are removed; every type node instead carries an optional transforms: transformNode[] applied innermost-first.
    • Instruction data. instructionArgumentNode, instructionArgumentLinkNode, resolverValueNode and instructionNode.arguments/extraArguments are removed; instruction arguments live in instructionNode.data, reached via path expressions. argumentValueNode → dataValueNode; accountFieldValueNode → accountDataValueNode.
    • Enum variants unified. The three variant nodes collapse into a single enumVariantTypeNode with an optional data.
    • Text and docs. docs and text-bearing attributes are the union string | textNode; a textNode carries structured metadata (plugins). New textNode.
    • Universal plugins. Every node gains an optional plugins: pluginNode[] base attribute — the extension point for renderer-specific or not-yet-standardised metadata.
    • Other. programNode.origin removed (provenance moves to plugins); pluginNode.name → pluginNode.namespace; sentinelCountNode added; path expressions for field references and discriminators.

    Node factory ergonomics: trailing optional parameters now live in the constructor's options bag (e.g. constantNode(identifier, type, value, { docs }), accountLinkNode(identifier, { program })).

Minor Changes

  • #1248 7f8beda Thanks @lorisleiva! - Add NodeStack helpers to jump to other parts of the tree, pass the visitor itself to interceptors, and add helpers to record and list linkable nodes.

    • stack.withPath(path, callback) runs callback with path as the current path, then restores the previous one, even if callback throws.
    • stack.visitPath(path, visitor) visits the last node of path with the rest of path as the current path, e.g. to visit the definition of a linked node: stack.visitPath(linkables.getPathOrThrow(linkPath), self).
    • VisitorInterceptor callbacks, used by interceptVisitor and interceptFirstVisitVisitor, receive the intercepted visitor as a third self argument, so they can visit other nodes with the whole visitor.
    • getLinkablesFromNode(node) records the linkable nodes under a node into a new LinkableDictionary, replacing new LinkableDictionary() followed by visit(node, getRecordLinkablesVisitor(linkables)).
    • linkables.getRecordedPathsOfKind(kind) lists the paths of every recorded linkable node of a kind, across all programs, e.g. getRecordedPathsOfKind('accountNode').

    Also fix two NodeStack issues: recordNodeStackVisitor now restores the stack when a visit throws, and visitors following links (getByteSizeVisitor, getMaxByteSizeVisitor) no longer put the linked node twice on the path.

  • #1248 7f8beda Thanks @lorisleiva! - Add helpers to detect cyclic defined types and defined types without any finite value, and fix cycle detection in the byte-size visitors across programs.

    • getDefinedTypeLinksVisitor(linkables, { stack? }) lists the paths of the defined types a node links to, without following these links.
    • getHasFiniteValueVisitor(linkables, { stack? }) tells whether a type has at least one finite value, following links: options, enums and collections without a fixed positive count stop a recursion, whereas a link back to a type being visited (e.g. loop = struct { next: link(loop) }) or an enum without variants has none.
    • isDefinedTypeCyclic(path, linkables) tells whether a defined type links back to itself, directly or through other defined types.
    • hasDefinedTypeFiniteValue(path, linkables) tells whether a defined type has at least one finite value.

    isDefinedTypeCyclic and hasDefinedTypeFiniteValue resolve links from the program of the given path and throw CODAMA_ERROR__NODE_PATH_PROGRAM_MISSING when it has none.

    getByteSizeVisitor and getMaxByteSizeVisitor now track the defined types being visited by node rather than by name, so a link to a same-named type of another program is no longer mistaken for a cycle, and links back to a type being visited still return null.

Patch Changes

Don't miss a new codama release

NewReleases is sending notifications on new releases.