Major Changes
-
#1262
e1cce0dThanks @lorisleiva! - Remove the deprecated node kind aliases, theProgramVersiontype and the duplicate node path formatters. Each has a canonical replacement that was already exported.BREAKING CHANGES
Node kind aliases removed. The legacy
*_NODESaliases of@codama/nodesare removed in favour of the*_NODE_KINDSconstants 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';
ProgramVersionremoved. Use theVersiontype instead.- import type { ProgramVersion } from '@codama/node-types'; + import type { Version } from '@codama/node-types';
Node path formatters removed.
nodePathToStringandnodePathToStringArrayare removed from@codama/visitors-core. UsegetNodePathStringfrom@codama/errors, which renders paths the way error messages do, orNodeStack#toStringfor 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
64d52ebThanks @codama-releases! - Release Codama v2, supporting IDLs of the v2 standard. Upgrade v1 IDLs with thecodama upgradecommand or the@codama/upgradepackage. -
#1264
909960fThanks @lorisleiva! - Trim public exports that were only meant for internal use, so the public API only exposes what consumers need.BREAKING CHANGES
@codama/errorsno longer exportssafeCaptureStackTrace. It was unused, even byCodamaError, and is also gone fromcodama, which re-exports it. CallError.captureStackTracedirectly if you need it.@codama/fragments/javascriptno longer exportspreferIncoming. UsemergeImportMapsoraddToImportMap, which apply the same conflict-resolution rule.@codama/dynamic-address-resolutionno longer exportsOPTIONAL_NODE_KINDS. Check the type node kind directly, e.g.isNode(type, ['optionTypeNode', 'remainderOptionTypeNode', 'zeroableOptionTypeNode']).@codama/nodes-from-anchoronly exports its root and program converters. The per-node converters, their options types and the generics helpers are now internal. Convert whole IDLs withrootNodeFromAnchor(orrootNodeFromAnchorV00,rootNodeFromAnchorV01,programNodeFromAnchorV00andprogramNodeFromAnchorV01) 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,typeNodeFromAnchorV00andInstructionAccountNodeFromAnchorV00Options.accountNodeFromAnchorV01,arrayTypeNodeFromAnchorV01,constantNodeFromAnchorV01,definedTypeNodeFromAnchorV01,enumTypeNodeFromAnchorV01,enumVariantTypeNodeFromAnchorV01,errorNodeFromAnchorV01,eventNodeFromAnchorV01,instructionAccountNodeFromAnchorV01,instructionAccountNodesFromAnchorV01,instructionNodeFromAnchorV01,optionTypeNodeFromAnchorV01,pdaSeedNodeFromAnchorV01,structFieldTypeNodeFromAnchorV01,structTypeNodeFromAnchorV01,tupleTypeNodeFromAnchorV01,typeNodeFromAnchorV01,unwrapGenericTypeFromAnchorV01,extractGenerics,GenericsV01,InstructionAccountNodeFromAnchorV01OptionsandPdaSeedNodeFromAnchorV01Options.extractPdasFromProgram: useextractPdasVisitorinstead.
- 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-coreno longer exportsLINKABLE_NODES,getDependenciesandassertNodePathHasProgram. These are also gone from@codama/visitorsandcodama, which re-export it. UsefindProgramNodeFromPathto check that a path has a program, andgetResolvedInstructionInputsVisitorto resolve instruction input dependencies. -
#1261
bb09077Thanks @lorisleiva! - Carry node paths in the remaining error contexts. Contexts that still held nodes or account names now hold node paths instead, e.g. thecycleof 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.getInstructionDataFieldsnow returns thefieldPathof each field, andmapVisitorgives 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)cycleholds the path of each account and data field instead of the nodes themselvesVISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY(1200004)dependencyuse dependencyKindanddependencyNameVISITORS__INVALID_NUMBER_WRAPPER(1200006)pathof the number to wrap (empty when the wrapper is invalid on its own)VISITORS__FAILED_TO_VALIDATE_NODE(1200008)pathof the validated nodeDYNAMIC_CLIENT__INSTRUCTION_NOT_FOUND(2500000)pathof the programDYNAMIC_CLIENT__PDA_NOT_FOUND(2500001)pathof the programDYNAMIC_CLIENT__UNEXPECTED_ADDRESS_TYPE(2500005)accountNamepathof the account, remaining accounts, PDA or data field, and theindexof the remaining account at faultDYNAMIC_CLIENT__CIRCULAR_ACCOUNT_DEPENDENCY(2500008)chainpathof the instruction,cycle,formattedCycletoAddressOrThrowtakes a node path. The second argument oftoAddressOrThrowfrom@codama/dynamic-address-resolutionis 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
indexfor 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].becomesCircular dependency detected when resolving the accounts and data of instruction [programNode]token.[instructionNode]transfer. Got the following dependency cycle [a -> b -> a].. -
#1251
277eae1Thanks @lorisleiva! - Stop exporting the test-onlyNODE_TEST_PATHSconstant.BREAKING CHANGES
NODE_TEST_PATHSremoved. 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
277eae1Thanks @lorisleiva! - Clear theLinkableDictionarybefore each top-level visit ofrecordLinkablesOnFirstVisitVisitor, so a reused visitor no longer resolves links against previously visited trees. Also add aLinkableDictionary.clear()method.BREAKING CHANGES
recordLinkablesOnFirstVisitVisitorclears its dictionary.recordLinkablesOnFirstVisitVisitorclears the dictionary before each top-level visit, so linkables recorded into it beforehand are discarded; record extra linkables inside the visitor instead. -
#1260
3ad402aThanks @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 supportsrequire(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
7f8bedaThanks @lorisleiva! - AddProvidedScopeto resolveinjectedValueNodes from theprovidesof enclosing instructions, addgetInstructionDataFields, and reworkgetResolvedInstructionInputsVisitoraround instruction data fields and injected values.ProvidedScopeholds the lexical frames ofprovidedNodes visible at a point of the tree, one perinstructionNodewithprovides, the innermost frame winning.scope.resolve(node, { kinds })replaces every injection withinnode(itself included), using fallbacks when nothing is provided, and returnsundefinedwhen an injection resolves to nothing.scope.resolveOrThrow(node, { kinds })throwsCODAMA_ERROR__INJECTED_VALUE_NOT_PROVIDEDinstead. Resolved values of the wrong kind throwCODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE.scope.get(key),push,popandcloneare also available.recordProvidedScopeVisitor(visitor, scope)keeps aProvidedScopein sync with a traversal, andgetProvidedScopeFromPath(path)builds the scope of the instructions along a path.getInstructionDataFields(instructionPath, linkables)lists every struct field addressable within an instruction'sdata, with its full path (InstructionDataField), e.g.amount,configandconfig.fee, following defined type links.
BREAKING CHANGES
getResolvedInstructionInputsVisitortakes aLinkableDictionary. It is now its first argument, used to follow defined type links in the instruction'sdata. Its options gainstack(the ancestry of the visited instruction, which must contain itsprogramNodefor linked data to be followed) andscope(theProvidedScopeof enclosing instructions), andincludeDataArgumentValueNodesis renamedincludeDataValueNodes.- visit(instruction, getResolvedInstructionInputsVisitor({ includeDataArgumentValueNodes: true })); + visit(instruction, getResolvedInstructionInputsVisitor(linkables, { includeDataValueNodes: true, stack }));
Resolved inputs wrap their node.
ResolvedInstructionAccountno longer extendsInstructionAccountNode: the account is undernode, alongsidedependsOn,isPda,resolvedIsOptional,resolvedIsSignerand a newresolvedDefaultValuewith injections resolved.ResolvedInstructionArgumentis replaced byResolvedInstructionDataField(node,path,dependsOn,resolvedDefaultValue), for the fields of the instruction'sdata, 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.
InstructionDependencyis now exported and isAccountValueNode | DataValueNode(previouslyAccountValueNode | ArgumentValueNode), andgetInstructionDependencies(input)is replaced bygetDependencies(defaultValue), which takes the default value to inspect rather than an instruction or input. -
#1248
7f8bedaThanks @lorisleiva! - AddresolveTypePathto follow path expressions such asconfig.fees[0]through types, andisValueNodeOfTypeto check a value node against a type.NodePathno longer distributes over node unions,nodePathToStringrenders paths like error messages do, and node selectors match identifiers exactly.New helpers:
resolveTypePath(source, path, linkables, options?)resolves aPathStringagainst the type at the end ofsourceand 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.datasegment on a public key carrying anaccountLinkcontinues into the linked account's data, e.g.mint.data.decimals. It throwsCODAMA_ERROR__CANNOT_RESOLVE_PATHwhen a segment cannot be followed.parsePath(path)splits a path expression intoPathSegments ({ kind: 'field', identifier }or{ kind: 'index', index }).TypePathNode(a registered type node or adefinedTypeLinkNode) andTypePath, a path ending at one.isValueNodeOfType(value, typePath, linkables)tells whether a concrete value node structurally fits the type at the end oftypePath, following defined type links, e.g.noneValueNode()for anoptionTypeNode.assertNodePathHasProgram(path)throwsCODAMA_ERROR__NODE_PATH_PROGRAM_MISSINGwhen a path has noprogramNodeto resolve links from.
BREAKING CHANGES
NodePathis no longer distributive.NodePath<A | B>is now one path ending atA | Brather than the unionNodePath<A> | NodePath<B>, so paths built from nodes only known as a union, e.g.[...stack.getPath(), node], type-check. Parameters typed asNodePath<A> | NodePath<B>no longer accept such paths and should useNodePath<A | B>.- function visitLink(path: NodePath<AccountLinkNode> | NodePath<PdaLinkNode>) {} + function visitLink(path: NodePath<AccountLinkNode | PdaLinkNode>) {}
nodePathToStringoutput changed. It now renders paths likegetNodePathStringfrom@codama/errors: node selectors joined by dots, leaving out a leadingrootNode.nodePathToStringArrayrenders each node with itsidentifier.- 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_authorityin[instructionAccountNode]mint_authority, is no longer converted to camelCase before being compared, and is matched against the node'sidentifier. -
#1158
08d72e7Thanks @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 carriesidentifierinstead ofname, and identifiers preserve their casing — v2 no longer mandates camelCase (transfer_tokensandtransferTokensare both valid; uniqueness is resolved by case-folding).- Numeric system rework.
numberTypeNode/numberValueNodesplit intointegerTypeNode/floatTypeNodeandintegerValueNode/floatValueNode(numeric values are string-encoded to stay lossless through JSON).amountTypeNode/solAmountTypeNodebecomefixedPointTypeNode; newdurationTypeNode;dateTimeTypeNode/durationTypeNodecarryticksPerSecond. - Flat transforms. The wrapper type nodes (
fixedSizeTypeNode,sizePrefixTypeNode,pre/postOffsetTypeNode,sentinelTypeNode,hiddenPrefix/SuffixTypeNode) are removed; every type node instead carries an optionaltransforms: transformNode[]applied innermost-first. - Instruction data.
instructionArgumentNode,instructionArgumentLinkNode,resolverValueNodeandinstructionNode.arguments/extraArgumentsare removed; instruction arguments live ininstructionNode.data, reached via path expressions.argumentValueNode→dataValueNode;accountFieldValueNode→accountDataValueNode. - Enum variants unified. The three variant nodes collapse into a single
enumVariantTypeNodewith an optionaldata. - Text and docs.
docsand text-bearing attributes are the unionstring | textNode; atextNodecarries structured metadata (plugins). NewtextNode. - Universal plugins. Every node gains an optional
plugins: pluginNode[]base attribute — the extension point for renderer-specific or not-yet-standardised metadata. - Other.
programNode.originremoved (provenance moves to plugins);pluginNode.name→pluginNode.namespace;sentinelCountNodeadded; path expressions for field references and discriminators.
Node factory ergonomics: trailing optional parameters now live in the constructor's
optionsbag (e.g.constantNode(identifier, type, value, { docs }),accountLinkNode(identifier, { program })).
Minor Changes
-
#1248
7f8bedaThanks @lorisleiva! - AddNodeStackhelpers 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)runscallbackwithpathas the current path, then restores the previous one, even ifcallbackthrows.stack.visitPath(path, visitor)visits the last node ofpathwith the rest ofpathas the current path, e.g. to visit the definition of a linked node:stack.visitPath(linkables.getPathOrThrow(linkPath), self).VisitorInterceptorcallbacks, used byinterceptVisitorandinterceptFirstVisitVisitor, receive the intercepted visitor as a thirdselfargument, so they can visit other nodes with the whole visitor.getLinkablesFromNode(node)records the linkable nodes under a node into a newLinkableDictionary, replacingnew LinkableDictionary()followed byvisit(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
NodeStackissues:recordNodeStackVisitornow restores the stack when a visit throws, and visitors following links (getByteSizeVisitor,getMaxByteSizeVisitor) no longer put the linked node twice on the path. -
#1248
7f8bedaThanks @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.
isDefinedTypeCyclicandhasDefinedTypeFiniteValueresolve links from the program of the given path and throwCODAMA_ERROR__NODE_PATH_PROGRAM_MISSINGwhen it has none.getByteSizeVisitorandgetMaxByteSizeVisitornow 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 returnnull.
Patch Changes
- #1251
277eae1Thanks @lorisleiva! - FixProvidedScoperesolving the fallback of a nested injection before the injection itself, which reported the fallback's key as missing even when the injection's own key was provided. - Updated dependencies [
1f0fb88,3f76a7f,e1cce0d,64d52eb,e92df74,7f8beda,909960f,3f76a7f,bb09077,bb09077,3ad402a,7f8beda,b869cef,7f8beda,7f8beda,08d72e7]:- @codama/errors@2.0.0-rc.0
- @codama/fragments@2.0.0-rc.0
- @codama/nodes@2.0.0-rc.0