@codama/renderers-js is a code generator that turns Codama IDL ASTs into TypeScript/JavaScript Solana client packages. The generated clients target @solana/kit APIs and are usually written into src/generated inside a consumer package.
The package itself is the generator, not a generated client.
src/index.ts: public exports; default export isrenderVisitor.src/visitors/renderVisitor.ts: top-level orchestration for generation.src/visitors/getRenderMapVisitor.ts: builds the full file map for a Codama root/program tree.src/visitors/getTypeManifestVisitor.ts: lowers Codama type/value nodes into reusable TS/codec/value fragments.
Think of the repo as a 3-layer generator:
getTypeManifestVisitor: decide how a node should be represented in TS/codecs/values.fragments/*: assemble those representations into account/instruction/type/program pages.getRenderMapVisitor+renderVisitor: decide which files exist, then format, sync dependencies, and write them.
- Caller invokes
renderVisitor(packageFolder, options). - Output folder defaults to
packageFolder/src/generated. - Existing generated folder is deleted unless
deleteFolderBeforeRendering === false. getRenderMapVisitor(options)walks the Codama tree and returns aRenderMap<Fragment>.- Each file is wrapped with the autogenerated header and final import block via
getPageFragment. - Code is optionally formatted with Prettier.
package.jsonis optionally created/updated based on actual imports used by generated files.- Files are written to disk.
src/visitors/: traversal/orchestration layer.src/fragments/: page and helper fragment builders.src/utils/: shared rendering primitives and policies.src/types/: ambient/global TS declarations.test/: unit tests for output generation and utility behavior.test/e2e/: real generated packages validated end-to-end against Solana flows..changeset/: release notes and versioning metadata..github/workflows/main.yml: CI and release automation.
Defined in src/utils/fragment.ts.
A Fragment is the fundamental render unit:
content: code stringimports: deferred import mapfeatures: extra generation flags
Fragments are immutable and composable. Most codegen helpers return fragments instead of raw strings.
Defined in src/utils/importMap.ts.
Imports are accumulated symbolically during generation and only materialized when building the final page. This enables deduping, type/non-type import collapsing, aliasing, and late resolution of module paths.
Produced by getTypeManifestVisitor.
Canonical representation of a type/value lowering. A manifest may contain:
encoderdecoderstrictTypelooseTypevalueisEnum
Fragments that need type semantics should ask the type manifest visitor for a manifest instead of reimplementing encoding/decoding logic.
Defined in src/utils/options.ts.
Shared context passed to fragments. Includes naming rules, dependency maps, custom-data config, import strategy, link resolution, async resolver info, and the type manifest visitor.
Comes from @codama/renderers-core.
Maps relative output file paths to fragments/content before final writing.
renderVisitor.ts: orchestration with side effects.getRenderMapVisitor.ts: generates output files for root/program/account/instruction/pda/type nodes.getTypeManifestVisitor.ts: semantic lowering engine for Codama types and values.index.ts: visitor exports.
Most files are small pure helpers that generate part of one page.
Main page builders:
- accounts:
accountPage,accountType,accountFetchHelpers,accountPdaHelpers,accountSizeHelpers - instructions:
instructionPage,instructionType,instructionFunction,instructionInputType,instructionInputResolved,instructionData,instructionParseFunction,instructionRemainingAccounts,instructionByteDelta,instructionExtraArgs - types:
typePage,type,typeCodec,typeDecoder,typeEncoder,typeWithCodec,typeDiscriminatedUnionHelpers - programs:
programPage,programConstant,programAccounts,programInstructions,programPlugin - misc:
errorPage,pdaPage,indexPage,rootIndexPage, discriminator helpers
Page builders mostly compose subfragments rather than embedding all logic inline.
fragment.ts: fragment creation/merging and final page wrapper with the autogenerated warning header.importMap.ts: symbolic import collection and module resolution.nameTransformers.ts: naming policy and override support.options.ts: public render options and shared scope types.packageJson.ts: derive dependency requirements from actual imports and sync/create package.json.customData.ts: support extracting or importing account/instruction data types from elsewhere.linkOverrides.ts: remap generated/internal import targets.async.ts: decide whether instructions need async variants and compute resolver dependencies.formatCode.ts: Prettier integration.typeManifest.ts: helpers for building and merging type manifests.codecs.ts: codec-related helpers.
Generated trees typically contain:
accounts/errors/instructions/pdas/programs/types/index.ts
Root index generation is centralized in src/fragments/rootIndexPage.ts. internalNodes can suppress exports while still rendering files.
Naming is centralized in src/utils/nameTransformers.ts. Do not hardcode names in new code if a nameApi function already exists.
Important defaults:
- files:
camelCase(name).ts - data types:
PascalCaseName - loose/input data types:
PascalCaseNameArgs - encoder:
getPascalCaseNameEncoder - decoder:
getPascalCaseNameDecoder - codec:
getPascalCaseNameCodec - sync instruction builder:
getPascalCaseNameInstruction - async instruction builder:
getPascalCaseNameInstructionAsync - instruction input types:
PascalCaseNameInput/PascalCaseNameAsyncInput - PDA finder:
findPascalCaseNamePda - program address constant:
PROGRAM_NAME_PROGRAM_ADDRESS - default discriminated union discriminator:
__kind
These are overrideable via the nameTransformers option.
Codegen uses symbolic module names such as:
generatedAccountsgeneratedInstructionsgeneratedProgramsgeneratedTypessolanaAddressessolanaCodecsCore
These are resolved late by importMap.ts according to:
- built-in internal/external defaults
kitImportStrategy- user
dependencyMap
kitImportStrategy values:
granular: prefer standalone@solana/*packagespreferRoot: prefer@solana/kitroot exports, fallback to granular/subpaths as neededrootOnly: only use@solana/kitand its subpaths
Dependency syncing in src/utils/packageJson.ts inspects final imports and updates package dependencies accordingly. @solana/kit is added to peerDependencies by default when introduced automatically.
src/visitors/getTypeManifestVisitor.ts is the semantic core of the generator. It handles:
- arrays, sets, maps, tuples, structs
- enums and discriminated unions
- options, zeroable options, remainder options
- bytes, strings, booleans, numbers, public keys, lamports
- size prefixes, fixed sizes, offsets, sentinels, hidden prefixes/suffixes
- constant/default value lowering
- links to previously defined types
When changing type behavior, start here first.
Important invariant: page/fragment generators should generally consume manifests, not re-derive encoding rules themselves.
Instruction generation is one of the more complex parts of the codebase. Depending on the instruction, output may include:
- instruction input types
- resolved input helpers
- instruction type aliases
- data encoders
- account metadata builders
- default value handling
- remaining account handling
- byte delta helpers
- sync and optionally async builders
- parse helpers
Async generation is controlled by src/utils/async.ts.
Async instruction variants are emitted when needed, especially for:
- async resolvers
- PDA-derived defaults
- async remaining accounts
- async byte deltas
Program pages aggregate program-level helpers, including:
- program address constants
- account identification helpers
- instruction identification and parse helpers
- plugin types/helpers
- error constants and error message helpers
Look at src/fragments/programPage.ts and its subfragments for program-level composition.
Defined via src/utils/customData.ts.
These let callers:
- import data definitions from an external module
- optionally extract generated account/instruction data into dedicated defined types
Default import source for custom data is hooked unless overridden.
Defined in src/utils/linkOverrides.ts.
Lets callers reroute imports for linked accounts, defined types, instructions, PDAs, programs, and resolvers.
Used to control how symbolic modules resolve and what version ranges get written to generated package manifests.
Render nodes but omit them from barrel exports.
Controls whether nested parent instructions are rendered or whether only leaf instructions are emitted.
Unit tests live in test/ and mainly validate generated output strings/imports.
Notable groups:
- page-level tests:
accountsPage.test.ts,instructionsPage.test.ts,programsPage.test.ts,pdasPage.test.ts - type behavior:
test/types/*.test.ts - links:
test/links/*.test.ts - fragment/import/package-json utilities:
test/utils/*.test.ts
Common pattern:
- construct Codama nodes inline using
@codama/nodes - run
visit(..., getRenderMapVisitor(...)) - assert against generated content or imports
test/_setup.ts contains helpers that normalize code with standalone Prettier before asserting.
vitest.config.mts runs three projects:
browserusinghappy-domnodereact-native
E2E tests are excluded from Vitest.
test/e2e/ contains small generated-client packages such as anchor, system, memo, token, and dummy.
test/e2e/test.sh:
- starts
solana-test-validatoron port8899if needed - regenerates a project using
test/e2e/generate.cjs - runs
pnpm install && pnpm build && pnpm testinside each project
test/e2e/generate.cjs uses the built root package from dist/index.node.cjs, so root builds must succeed before E2E generation works.
E2E projects use AVA, not Vitest.
pnpm installpnpm buildpnpm lintpnpm lint:fixpnpm testpnpm test:typespnpm test:unitpnpm test:e2epnpm test:exportspnpm test:treeshakabilitypnpm dev
tsup.config.ts builds multiple platform/format bundles:
- node CJS/ESM
- browser CJS/ESM
- react-native ESM
Declarations are emitted separately into dist/types.
- Changesets drives versioning and changelog generation.
- Pending release notes live in
.changeset/*.md. pnpm publish-packageruns build thenchangeset publish.- GitHub Actions handles release PR/publish on pushes to
main.
From .github/workflows/main.yml:
- CI runs lint and the full test suite.
- CI requires the working tree to stay clean after build and tests.
That means if a code change affects generated outputs, fixtures, or checked-in artifacts, update them so git status --porcelain stays empty after validation.
- TypeScript is strict.
- Important compiler settings include
isolatedModules,isolatedDeclarations,noUnusedLocals,noUnusedParameters, andnoPropertyAccessFromIndexSignature. - Root uses
moduleResolution: "node". - E2E packages often use
moduleResolution: "bundler"because@solana/kitsubpath exports may require it. - ESLint uses
@solana/eslint-config-solana. - Prettier uses
@solana/prettier-config-solana.
If you need to...
- change overall generation flow:
src/visitors/renderVisitor.ts - add/remove emitted files:
src/visitors/getRenderMapVisitor.ts - change TS type or codec semantics:
src/visitors/getTypeManifestVisitor.ts - change import resolution:
src/utils/importMap.ts - change generated naming:
src/utils/nameTransformers.ts - change package dependency syncing:
src/utils/packageJson.ts - change page wrappers/header/import rendering:
src/utils/fragment.ts - change account output:
src/fragments/account*.ts - change instruction output:
src/fragments/instruction*.ts - change program output:
src/fragments/program*.ts - change type page output:
src/fragments/type*.ts - understand expected behavior:
test/andtest/e2e/
- Prefer extending existing fragment/visitor patterns over introducing one-off string builders.
- Reuse
nameApiand import helpers instead of hardcoding names/imports. - Keep type semantics centralized in
getTypeManifestVisitorwhen possible. - Preserve fragment immutability and compositional style.
- When changing generated output, update or add tests that assert the new behavior.
- Be mindful that E2E generation consumes the built package from
dist, notsrcdirectly. - Respect CI’s clean-working-tree invariant.
- Package manager:
pnpm@10.15.1 - Required Node:
>=20.18.0 - Package type:
commonjs - Published files: built entrypoints and
dist/types - Default generated folder in consumer packages:
src/generated - Generated files always include an autogenerated warning header
The following instructions come from installed skills (autogenerated, do not edit manually) and should always be followed.
- Any PR that should trigger a package release MUST include a changeset.
- Identify affected packages by mapping changed files to their nearest
package.json. - Choose the right bump:
patchfor fixes,minorfor features,majorfor breaking changes. - While a project is pre-1.0,
minorbumps may be treated as breaking. - ALWAYS use
npx changeset add --emptyto generate a new changeset file with a random name. NEVER create changeset files manually. - No changeset needed for: docs-only changes, CI config, dev dependency updates, test-only changes.
- NEVER commit, push, create branches, or create PRs without explicit user approval.
- Before any git operation that creates or modifies a commit, present a review block containing: changeset entry (if applicable), commit title, and commit/PR description. ALWAYS wait for approval.
- Use standard
git addandgit commitworkflows. Concise title on the first line, blank line, then description body. - Use
gh pr createfor pull requests. - Write commit and PR descriptions as natural flowing prose. Do NOT insert hard line breaks mid-paragraph.
- Check if Graphite is installed (
which gt). Prefer Graphite when available; fall back to the Git shipping workflow otherwise. - NEVER commit, push, create branches, or create PRs without explicit user approval.
- Before any git operation that creates or modifies a commit, present a review block containing: changeset entry (if applicable), commit title, and commit/PR description. ALWAYS wait for approval.
- Use
gt create -am "Title" -m "Description body"for new PRs. The first-msets the commit title; the second sets the PR description. - Use
gt modify -ato amend the current branch with follow-up changes (NEVER create additional commits on the same branch). - ALWAYS escape backticks in commit messages with backslashes for shell compatibility (e.g.
"Update \my-package` config"`). - Do NOT run
gt submitafter committing. Only run it when the user explicitly asks to submit or push. - Write commit and PR descriptions as natural flowing prose. Do NOT insert hard line breaks mid-paragraph.
- All exported functions, types, interfaces, and constants MUST have JSDoc docblocks.
- Start with
/**, use*prefix for each line, end with*/— each on its own line. - Begin with a clear one-to-two line summary. Add a blank line before tags.
- Include
@param,@typeParam,@return,@throws, and at least one@examplewhen helpful. - Use
{@link ...}to reference related items. Add@seetags at the end for related APIs.
- When adding a new public API, add or update the package's README.
- Structure: brief intro, installation, usage (with code snippet), deep-dive sections.
- Code snippets must be realistic, concise, and use TypeScript syntax.
- Focus on the quickest path to success. Developers should feel excited, not overwhelmed.