Skip to content

Regenerate @codama/nodes constructors from @codama/spec#997

Open
lorisleiva wants to merge 1 commit into
05-08-introduce_codama-internal_spec-generators_and_regenerate_codama_node-types_from
05-12-regenerate_codama_nodes_constructors_from_codama_spec_
Open

Regenerate @codama/nodes constructors from @codama/spec#997
lorisleiva wants to merge 1 commit into
05-08-introduce_codama-internal_spec-generators_and_regenerate_codama_node-types_from
05-12-regenerate_codama_nodes_constructors_from_codama_spec_

Conversation

@lorisleiva
Copy link
Copy Markdown
Member

This PR extends @codama-internal/spec-generators with a second generator that produces the xxxNodeInput types, xxxNode() constructors, and runtime *_NODE_KINDS arrays of @codama/nodes from the encoded @codama/spec description. The bulk of packages/nodes/src/ now lives under src/generated/ and is rewritten on every pnpm generate run; the hand-written helper files (isNode/assertIsNode, getAllPrograms, isScalarEnum, the NestedTypeNode resolvers, etc.) survive at the top of src/ and re-export the matching generated constructor so the public import surface is unchanged.

The new generator lives at packages/spec-generators/src/nodes/. It consults a per-node configuration table (getNodeConfigs(spec)) that encodes the conveniences the spec doesn't carry — positional vs. object-input shape, defaulted attributes, camelCase wrapping, conditional-spread patterns — and derives the Partial<> wrapping of each Input type automatically. Runtime kinds-arrays are generated from the spec's UnionSpecs, with union-of-unions transitively expanded via spreads; the top-level REGISTERED_NODE_KINDS aggregate is composed from each category's Registered<Category>Node array.

Two intentional behaviour changes shake out of the rebuild:

  • docs is now omitted entirely from the encoded shape when it would be empty. Matches the Rust side and keeps absent documentation out of serialised IDLs. removeDocsVisitor in @codama/visitors-core is updated to delete the docs key rather than blank it to [], following the same convention.
  • rootNode().version now reflects the spec version @codama/nodes was generated against, not the runtime package version. The constructor previously read the __VERSION__ build-time global; it now bakes the spec version as a literal at generation time. Invisible at HEAD since the two have always tracked the same release cadence, but it makes the intent explicit.

The legacy plural-noun constants (TYPE_NODES, VALUE_NODES, CONTEXTUAL_VALUE_NODES, INSTRUCTION_INPUT_VALUE_NODES, COUNT_NODES, DISCRIMINATOR_NODES, LINK_NODES, PDA_SEED_NODES, ENUM_VARIANT_TYPE_NODES) are preserved as alias re-exports of the new canonical *_NODE_KINDS names.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

🦋 Changeset detected

Latest commit: 6cb21cf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@codama/nodes Minor
@codama/visitors-core Minor
@codama/cli Patch
@codama/dynamic-codecs Patch
@codama/dynamic-parsers Patch
codama Minor
@codama/nodes-from-anchor Patch
@codama/renderers-core Patch
@codama/validators Minor
@codama/visitors Minor
@codama/dynamic-client Patch
@codama/errors Minor
@codama/node-types Minor
@codama/fragments Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lorisleiva lorisleiva force-pushed the 05-12-regenerate_codama_nodes_constructors_from_codama_spec_ branch 2 times, most recently from c4ba9f0 to 42d953d Compare May 13, 2026 23:14
This PR extends `@codama-internal/spec-generators` with a second generator that produces the `xxxNodeInput` types, `xxxNode()` constructors, and runtime `*_NODE_KINDS` arrays of `@codama/nodes` from the encoded `@codama/spec` description. The bulk of `packages/nodes/src/` now lives under `src/generated/` and is rewritten on every `pnpm generate` run; the hand-written helper files (`isNode`/`assertIsNode`, `getAllPrograms`, `isScalarEnum`, the `NestedTypeNode` resolvers, etc.) survive at the top of `src/` and re-export the matching generated constructor so the public import surface is unchanged.

The new generator lives at `packages/spec-generators/src/nodes/`. It consults a per-node configuration table (`getNodeConfigs(spec)`) that encodes the conveniences the spec doesn't carry — positional vs. object-input shape, defaulted attributes, `camelCase` wrapping, conditional-spread patterns — and derives the `Partial<>` wrapping of each `Input` type automatically. Runtime kinds-arrays are generated from the spec's `UnionSpec`s, with union-of-unions transitively expanded via spreads; the top-level `REGISTERED_NODE_KINDS` aggregate is composed from each category's `Registered<Category>Node` array.

Two intentional behaviour changes shake out of the rebuild:

- **`docs` is now omitted entirely from the encoded shape when it would be empty.** Matches the Rust side and keeps absent documentation out of serialised IDLs. `removeDocsVisitor` in `@codama/visitors-core` is updated to delete the `docs` key rather than blank it to `[]`, following the same convention.
- **`rootNode().version` now reflects the spec version `@codama/nodes` was generated against, not the runtime package version.** The constructor previously read the `__VERSION__` build-time global; it now bakes the spec version as a literal at generation time. Invisible at HEAD since the two have always tracked the same release cadence, but it makes the intent explicit.

The legacy plural-noun constants (`TYPE_NODES`, `VALUE_NODES`, `CONTEXTUAL_VALUE_NODES`, `INSTRUCTION_INPUT_VALUE_NODES`, `COUNT_NODES`, `DISCRIMINATOR_NODES`, `LINK_NODES`, `PDA_SEED_NODES`, `ENUM_VARIANT_TYPE_NODES`) are preserved as alias re-exports of the new canonical `*_NODE_KINDS` names.
@lorisleiva lorisleiva force-pushed the 05-12-regenerate_codama_nodes_constructors_from_codama_spec_ branch from 42d953d to 6cb21cf Compare May 14, 2026 08:27
@lorisleiva lorisleiva marked this pull request as ready for review May 14, 2026 08:33
@lorisleiva
Copy link
Copy Markdown
Member Author

@trevor-cortex

Copy link
Copy Markdown

@trevor-cortex trevor-cortex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This is a large but largely mechanical rebuild: it pivots @codama/nodes from hand-maintained constructors + kinds-arrays to a second generator (nodes) inside @codama-internal/spec-generators, driven by @codama/spec. The bulk of the diff is generated output under packages/nodes/src/generated/. The hand-written ergonomics layer (isNode, assertIsNode, getAllPrograms, parseOptionalAccountStrategy, the NestedTypeNode resolvers, the constantXxxNodeFromString/FromBytes flavours, the structFieldTypeNodeFromInstructionArgumentNode helper, the scalar/data enum predicates, etc.) survives at the top of src/ and the public import surface is preserved via re-exports plus legacy plural-noun aliases.

The approach reads well: signature shapes, generic-parameter order, Partial<> wrapping, the name/docs input relaxations, and conditional-spread of optional attributes are all derived from the spec, with a minimal per-node config table carrying only the conveniences the spec can't express (positional vs. options bag, defaulted values, link-target string coercions, the one bespoke body for instructionByteDeltaNode). Lining up narrowableDataAttributes + genericParamOrder with the nodeTypes generator keeps the constructor generics in lockstep with the interface generics, which is the right move.

Intentional behaviour changes

Both are documented and read as the right defaults going forward, but they're worth flagging again for downstream reviewers:

  1. docs is now omitted when empty. All generated constructors do ...(parsedDocs.length > 0 && { docs: parsedDocs }) instead of unconditionally setting docs: parseDocs(input.docs). The removeDocsVisitor update matches this convention by deleting the key rather than blanking to []. The Docs type was already declared docs?: Docs in @codama/node-types, so callers and renderers should already handle undefined — but anything in the wider ecosystem that did node.docs.length (which would have always worked in practice on freshly-constructed nodes) will now throw. Worth a heads-up to renderer maintainers downstream.

  2. rootNode().version now reads the generated CODAMA_VERSION literal instead of __VERSION__. Practically invisible at HEAD because the spec version and package version track together, but it means pnpm generate is now a hard prerequisite for cutting a release that bumps the embedded version. Worth considering a release-tooling guard (or CI check) that regenerates and fails on drift, so a future package bump without a regenerate doesn't silently lag the embedded version.

Things to watch out for on re-review

  • Generated-vs-old type surface equivalence. I spot-checked the constructors I'd expect to drift (accountNode, eventNode, instructionAccountNode, instructionNode, programNode, instructionArgumentNode, constantDiscriminatorNode) and didn't find any breaking input-shape changes — the Partial<> wrapping only relaxes fields that were already optional in @codama/node-types, and the new const generics + ConstantDiscriminatorNode<TConstant> narrowing are strict improvements. The eventNode lost its TData = ReturnType<typeof structTypeNode> default, but data was always required by the input type so the default was unreachable. test:types running clean is the real signal here.

  • Legacy alias coverage. STANDALONE_TYPE_NODES doesn't appear in the alias re-export block in index.ts. If any external consumer imported that specific name it would now break. Worth a quick grep across downstream packages, but I'd be surprised — the old name was already STANDALONE_TYPE_NODE_KINDS, not the plural-noun flavour, so no rename was needed.

  • packages/nodes/src/types/global.d.ts is removed entirely, not just the __VERSION__ line. The other constants (__BROWSER__, __NODEJS__, __REACTNATIVE__, __ESM__, __TEST__) are still injected by tsup.config.base.ts for runtime, but they're now untyped inside this package. Confirmed by reading the new diff that nothing under packages/nodes/src/ references them — deleting the file is fine, and is actually a hygiene improvement — but a future contributor adding e.g. a __DEV__ branch under src/ will hit a TS error before the build error. Probably the right tradeoff, just noting it.

  • PR description nit: the description refers to getNodeConfigs(spec), but the exported API in packages/spec-generators/src/nodes/options.ts is a static NODE_CONFIGS constant (plus validateNodeConfigs). Description-only, doesn't affect the code.

Note for subsequent reviewers

  • The visitors-core change is small but high-blast-radius. removeDocsVisitor previously normalised every node's docs to []; it now deletes the key. Any visitor chained after removeDocsVisitor that read node.docs (rather than node.docs ?? []) will now see undefined. The patch bump in the changeset reflects this is treated as a non-breaking alignment, which is consistent with docs?: Docs being optional in the type — but worth a quick scan of downstream visitor pipelines.
  • Most of the diff is generated output; the signal-to-noise on the spec-generators side (packages/spec-generators/src/nodes/) is where the actual logic lives and deserves the closer read — in particular nodeConfigs (the convenience table) and the renderer pipeline (signature derivation, generic lifting, auto-imports). I didn't comment inline there since the diff alone doesn't capture enough context to second-guess the design.
  • No unit-test changes accompany the rebuild. test:types + test:treeshakability + the existing test:unit suite should be the safety net; assuming they're green on CI, this is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants