Regenerate @codama/nodes constructors from @codama/spec#997
Conversation
🦋 Changeset detectedLatest commit: 6cb21cf The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
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 |
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
c4ba9f0 to
42d953d
Compare
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.
42d953d to
6cb21cf
Compare
trevor-cortex
left a comment
There was a problem hiding this comment.
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:
-
docsis now omitted when empty. All generated constructors do...(parsedDocs.length > 0 && { docs: parsedDocs })instead of unconditionally settingdocs: parseDocs(input.docs). TheremoveDocsVisitorupdate matches this convention by deleting the key rather than blanking to[]. TheDocstype was already declareddocs?: Docsin@codama/node-types, so callers and renderers should already handleundefined— but anything in the wider ecosystem that didnode.docs.length(which would have always worked in practice on freshly-constructed nodes) will now throw. Worth a heads-up to renderer maintainers downstream. -
rootNode().versionnow reads the generatedCODAMA_VERSIONliteral instead of__VERSION__. Practically invisible at HEAD because the spec version and package version track together, but it meanspnpm generateis 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 — thePartial<>wrapping only relaxes fields that were already optional in@codama/node-types, and the newconstgenerics +ConstantDiscriminatorNode<TConstant>narrowing are strict improvements. TheeventNodelost itsTData = ReturnType<typeof structTypeNode>default, butdatawas always required by the input type so the default was unreachable.test:typesrunning clean is the real signal here. -
Legacy alias coverage.
STANDALONE_TYPE_NODESdoesn't appear in the alias re-export block inindex.ts. If any external consumer imported that specific name it would now break. Worth a quickgrepacross downstream packages, but I'd be surprised — the old name was alreadySTANDALONE_TYPE_NODE_KINDS, not the plural-noun flavour, so no rename was needed. -
packages/nodes/src/types/global.d.tsis removed entirely, not just the__VERSION__line. The other constants (__BROWSER__,__NODEJS__,__REACTNATIVE__,__ESM__,__TEST__) are still injected bytsup.config.base.tsfor runtime, but they're now untyped inside this package. Confirmed by reading the new diff that nothing underpackages/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 undersrc/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 inpackages/spec-generators/src/nodes/options.tsis a staticNODE_CONFIGSconstant (plusvalidateNodeConfigs). Description-only, doesn't affect the code.
Note for subsequent reviewers
- The visitors-core change is small but high-blast-radius.
removeDocsVisitorpreviously normalised every node'sdocsto[]; it now deletes the key. Any visitor chained afterremoveDocsVisitorthat readnode.docs(rather thannode.docs ?? []) will now seeundefined. The patch bump in the changeset reflects this is treated as a non-breaking alignment, which is consistent withdocs?: Docsbeing 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 particularnodeConfigs(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 existingtest:unitsuite should be the safety net; assuming they're green on CI, this is fine.

This PR extends
@codama-internal/spec-generatorswith a second generator that produces thexxxNodeInputtypes,xxxNode()constructors, and runtime*_NODE_KINDSarrays of@codama/nodesfrom the encoded@codama/specdescription. The bulk ofpackages/nodes/src/now lives undersrc/generated/and is rewritten on everypnpm generaterun; the hand-written helper files (isNode/assertIsNode,getAllPrograms,isScalarEnum, theNestedTypeNoderesolvers, etc.) survive at the top ofsrc/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,camelCasewrapping, conditional-spread patterns — and derives thePartial<>wrapping of eachInputtype automatically. Runtime kinds-arrays are generated from the spec'sUnionSpecs, with union-of-unions transitively expanded via spreads; the top-levelREGISTERED_NODE_KINDSaggregate is composed from each category'sRegistered<Category>Nodearray.Two intentional behaviour changes shake out of the rebuild:
docsis now omitted entirely from the encoded shape when it would be empty. Matches the Rust side and keeps absent documentation out of serialised IDLs.removeDocsVisitorin@codama/visitors-coreis updated to delete thedocskey rather than blank it to[], following the same convention.rootNode().versionnow reflects the spec version@codama/nodeswas 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_KINDSnames.