Skip to content

Commit f473e96

Browse files
committed
Regenerate @codama/nodes constructors from @codama/spec
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.
1 parent 9894cfa commit f473e96

241 files changed

Lines changed: 4761 additions & 1673 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@codama/nodes': minor
3+
'@codama/visitors-core': patch
4+
---
5+
6+
Regenerate the `xxxNodeInput` types and `xxxNode()` constructors of `@codama/nodes` from the encoded `@codama/spec` description, via a new `nodes` generator inside `@codama-internal/spec-generators`. The runtime `*_NODE_KINDS` arrays (`STANDALONE_TYPE_NODE_KINDS`, `REGISTERED_VALUE_NODE_KINDS`, `INSTRUCTION_INPUT_VALUE_NODE_KINDS`, …, and the top-level `REGISTERED_NODE_KINDS`) are now generated from the spec's union definitions instead of being maintained by hand. A new top-level `CODAMA_VERSION` constant, typed as `CodamaVersion` and pinned to the spec version at generation time, is the single source of truth for the version `@codama/nodes` was built against — `rootNode()` reads it directly when tagging the document.
7+
8+
The bulk of the surface lives under `packages/nodes/src/generated/` and is produced on every `pnpm generate` run. The previously hand-maintained constructors and kinds-arrays are gone; only hand-written helpers (`isNode`, `assertIsNode`, `getAllPrograms`, `getAllInstructions`, `getAllInstructionsWithSubs`, `isScalarEnum`, `isDataEnum`, `isSignedInteger`, the `NestedTypeNode` resolvers, `parseOptionalAccountStrategy`, the legacy `constantValueNodeFromString` / `constantPdaSeedNodeFromString` flavours, etc.) survive at the top of `packages/nodes/src/`. The package's `index.ts` re-exports the generated tree alongside them.
9+
10+
Two intentional behaviour changes shake out of the rebuild:
11+
12+
- **`docs` is now omitted entirely from the encoded shape when it would be empty.** Constructors that accept a `docs?: DocsInput` parameter previously emitted `docs: []` on the frozen node when the caller said nothing about docs; they now drop the `docs` key altogether. This matches the Rust side, keeps absent documentation out of serialised IDLs, and aligns with the `docs?: Docs` optional field already declared by `@codama/node-types`. `removeDocsVisitor` in `@codama/visitors-core` is updated to delete the `docs` key rather than blank it to `[]`, following the same convention.
13+
- **`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 injected from the package's `npm_package_version`; it now reads the generated `CODAMA_VERSION` constant. In practice the two have always tracked the same release cadence so the change is invisible at HEAD, but it makes the architectural intent explicit: the version pinned in the IDL is the spec version, not the package version. The `__VERSION__` build-time global and its `packages/nodes/src/types/global.d.ts` declaration are removed accordingly.
14+
15+
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.
16+
17+
The generator drives almost entirely from the spec, with a minimal per-node configuration table carrying only the conveniences the spec can't express: which spec attributes appear as bare positional parameters (the rest land in a trailing `options` bag), and per-attribute overrides for defaulted values, string-coercion patterns on link targets, and the handful of bespoke body expressions (`instructionByteDeltaNode.withHeader`). The renderer derives signature shapes, generic parameters, return types, the `XxxNodeInput` declarations, the `Partial<>` wrapping decision, the `name: string` relaxation, the `docs?: DocsInput` / drop-if-empty handling, and the conditional-spread of optional attributes from the spec directly. An auto-import scan walks each rendered file and pulls in any spec or hand-written identifier the source references, so the configuration never declares imports. Generic-parameter lifting and ordering rely on the same `narrowableDataAttributes` + `genericParamOrder` tables the `nodeTypes` generator uses, keeping the constructor's generics in lockstep with the interface's.

packages/nodes/src/ConstantNode.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { BytesEncoding } from '@codama/node-types';
2+
3+
import { programIdValueNode } from './generated/contextualValueNodes/ProgramIdValueNode';
4+
import { constantPdaSeedNode } from './generated/pdaSeedNodes/ConstantPdaSeedNode';
5+
import { bytesTypeNode } from './generated/typeNodes/BytesTypeNode';
6+
import { publicKeyTypeNode } from './generated/typeNodes/PublicKeyTypeNode';
7+
import { stringTypeNode } from './generated/typeNodes/StringTypeNode';
8+
import { bytesValueNode } from './generated/valueNodes/BytesValueNode';
9+
import { stringValueNode } from './generated/valueNodes/StringValueNode';
10+
11+
export function constantPdaSeedNodeFromProgramId() {
12+
return constantPdaSeedNode(publicKeyTypeNode(), programIdValueNode());
13+
}
14+
15+
export function constantPdaSeedNodeFromString<TEncoding extends BytesEncoding>(encoding: TEncoding, string: string) {
16+
return constantPdaSeedNode(stringTypeNode(encoding), stringValueNode(string));
17+
}
18+
19+
export function constantPdaSeedNodeFromBytes<TEncoding extends BytesEncoding>(encoding: TEncoding, data: string) {
20+
return constantPdaSeedNode(bytesTypeNode(), bytesValueNode(encoding, data));
21+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { BytesEncoding } from '@codama/node-types';
2+
3+
import { bytesTypeNode } from './generated/typeNodes/BytesTypeNode';
4+
import { stringTypeNode } from './generated/typeNodes/StringTypeNode';
5+
import { bytesValueNode } from './generated/valueNodes/BytesValueNode';
6+
import { constantValueNode } from './generated/valueNodes/ConstantValueNode';
7+
import { stringValueNode } from './generated/valueNodes/StringValueNode';
8+
9+
export function constantValueNodeFromString<TEncoding extends BytesEncoding>(encoding: TEncoding, string: string) {
10+
return constantValueNode(stringTypeNode(encoding), stringValueNode(string));
11+
}
12+
13+
export function constantValueNodeFromBytes<TEncoding extends BytesEncoding>(encoding: TEncoding, data: string) {
14+
return constantValueNode(bytesTypeNode(), bytesValueNode(encoding, data));
15+
}

packages/nodes/src/EnumTypeNode.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { EnumTypeNode } from '@codama/node-types';
2+
3+
export function isScalarEnum(node: EnumTypeNode): boolean {
4+
return node.variants.every(variant => variant.kind === 'enumEmptyVariantTypeNode');
5+
}
6+
7+
export function isDataEnum(node: EnumTypeNode): boolean {
8+
return !isScalarEnum(node);
9+
}

packages/nodes/src/InstructionArgumentNode.ts

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,16 @@
1-
import type { InstructionArgumentNode, InstructionInputValueNode } from '@codama/node-types';
1+
import type { InstructionArgumentNode } from '@codama/node-types';
22

3+
import { structFieldTypeNode } from './generated/typeNodes/StructFieldTypeNode';
4+
import { structTypeNode } from './generated/typeNodes/StructTypeNode';
5+
import { VALUE_NODE_KINDS } from './generated/valueNodes/ValueNode';
36
import { isNode } from './Node';
4-
import { camelCase, DocsInput, parseDocs } from './shared';
5-
import { structFieldTypeNode } from './typeNodes/StructFieldTypeNode';
6-
import { structTypeNode } from './typeNodes/StructTypeNode';
7-
import { VALUE_NODES } from './valueNodes';
8-
9-
export type InstructionArgumentNodeInput<
10-
TDefaultValue extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
11-
> = Omit<InstructionArgumentNode<TDefaultValue>, 'docs' | 'kind' | 'name'> & {
12-
readonly docs?: DocsInput;
13-
readonly name: string;
14-
};
15-
16-
export function instructionArgumentNode<TDefaultValue extends InstructionInputValueNode | undefined = undefined>(
17-
input: InstructionArgumentNodeInput<TDefaultValue>,
18-
): InstructionArgumentNode<TDefaultValue> {
19-
return Object.freeze({
20-
kind: 'instructionArgumentNode',
21-
22-
// Data.
23-
name: camelCase(input.name),
24-
...(input.defaultValueStrategy !== undefined && { defaultValueStrategy: input.defaultValueStrategy }),
25-
docs: parseDocs(input.docs),
26-
27-
// Children.
28-
type: input.type,
29-
...(input.defaultValue !== undefined && { defaultValue: input.defaultValue }),
30-
});
31-
}
327

338
export function structTypeNodeFromInstructionArgumentNodes(nodes: InstructionArgumentNode[]) {
349
return structTypeNode(nodes.map(structFieldTypeNodeFromInstructionArgumentNode));
3510
}
3611

3712
export function structFieldTypeNodeFromInstructionArgumentNode(node: InstructionArgumentNode) {
38-
if (isNode(node.defaultValue, VALUE_NODES)) {
13+
if (isNode(node.defaultValue, VALUE_NODE_KINDS)) {
3914
return structFieldTypeNode({ ...node, defaultValue: node.defaultValue });
4015
}
4116
return structFieldTypeNode({

packages/nodes/src/InstructionNode.ts

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,13 @@
11
import type {
2-
DiscriminatorNode,
3-
InstructionAccountNode,
42
InstructionArgumentNode,
5-
InstructionByteDeltaNode,
63
InstructionNode,
7-
InstructionRemainingAccountsNode,
84
OptionalAccountStrategy,
95
ProgramNode,
106
RootNode,
117
} from '@codama/node-types';
128

139
import { isNode } from './Node';
1410
import { getAllInstructions } from './ProgramNode';
15-
import { camelCase, DocsInput, parseDocs } from './shared';
16-
17-
type SubInstructionNode = InstructionNode;
18-
19-
export type InstructionNodeInput<
20-
TAccounts extends InstructionAccountNode[] = InstructionAccountNode[],
21-
TArguments extends InstructionArgumentNode[] = InstructionArgumentNode[],
22-
TExtraArguments extends InstructionArgumentNode[] | undefined = InstructionArgumentNode[] | undefined,
23-
TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined =
24-
| InstructionRemainingAccountsNode[]
25-
| undefined,
26-
TByteDeltas extends InstructionByteDeltaNode[] | undefined = InstructionByteDeltaNode[] | undefined,
27-
TDiscriminators extends DiscriminatorNode[] | undefined = DiscriminatorNode[] | undefined,
28-
TSubInstructions extends SubInstructionNode[] | undefined = SubInstructionNode[] | undefined,
29-
> = Omit<
30-
Partial<
31-
InstructionNode<
32-
TAccounts,
33-
TArguments,
34-
TExtraArguments,
35-
TRemainingAccounts,
36-
TByteDeltas,
37-
TDiscriminators,
38-
TSubInstructions
39-
>
40-
>,
41-
'docs' | 'kind' | 'name'
42-
> & {
43-
readonly docs?: DocsInput;
44-
readonly name: string;
45-
};
46-
47-
export function instructionNode<
48-
const TAccounts extends InstructionAccountNode[] = [],
49-
const TArguments extends InstructionArgumentNode[] = [],
50-
const TExtraArguments extends InstructionArgumentNode[] | undefined = undefined,
51-
const TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined = undefined,
52-
const TByteDeltas extends InstructionByteDeltaNode[] | undefined = undefined,
53-
const TDiscriminators extends DiscriminatorNode[] | undefined = undefined,
54-
const TSubInstructions extends SubInstructionNode[] | undefined = undefined,
55-
>(
56-
input: InstructionNodeInput<
57-
TAccounts,
58-
TArguments,
59-
TExtraArguments,
60-
TRemainingAccounts,
61-
TByteDeltas,
62-
TDiscriminators,
63-
TSubInstructions
64-
>,
65-
): InstructionNode<
66-
TAccounts,
67-
TArguments,
68-
TExtraArguments,
69-
TRemainingAccounts,
70-
TByteDeltas,
71-
TDiscriminators,
72-
TSubInstructions
73-
> {
74-
return Object.freeze({
75-
kind: 'instructionNode',
76-
77-
// Data.
78-
name: camelCase(input.name),
79-
docs: parseDocs(input.docs),
80-
optionalAccountStrategy: parseOptionalAccountStrategy(input.optionalAccountStrategy),
81-
...(input.status !== undefined && { status: input.status }),
82-
83-
// Children.
84-
accounts: (input.accounts ?? []) as TAccounts,
85-
arguments: (input.arguments ?? []) as TArguments,
86-
extraArguments: input.extraArguments,
87-
remainingAccounts: input.remainingAccounts,
88-
byteDeltas: input.byteDeltas,
89-
discriminators: input.discriminators,
90-
subInstructions: input.subInstructions,
91-
});
92-
}
9311

9412
export function parseOptionalAccountStrategy(
9513
optionalAccountStrategy: OptionalAccountStrategy | undefined,

packages/nodes/src/typeNodes/NestedTypeNode.ts renamed to packages/nodes/src/NestedTypeNode.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { CODAMA_ERROR__UNEXPECTED_NESTED_NODE_KIND, CodamaError } from '@codama/errors';
22
import type { NestedTypeNode, Node, TypeNode } from '@codama/node-types';
33

4-
import { isNode } from '../Node';
5-
import { TYPE_NODES } from './TypeNode';
4+
import { TYPE_NODE_KINDS } from './generated/typeNodes/TypeNode';
5+
import { isNode } from './Node';
66

77
export function resolveNestedTypeNode<TType extends TypeNode>(typeNode: NestedTypeNode<TType>): TType {
88
switch (typeNode.kind) {
@@ -44,7 +44,7 @@ export function isNestedTypeNode<TKind extends TypeNode['kind']>(
4444
node: Node | null | undefined,
4545
kind: TKind | TKind[],
4646
): node is NestedTypeNode<Extract<TypeNode, { kind: TKind }>> {
47-
if (!isNode(node, TYPE_NODES)) return false;
47+
if (!isNode(node, TYPE_NODE_KINDS)) return false;
4848
const kinds = Array.isArray(kind) ? kind : [kind];
4949
const resolved = resolveNestedTypeNode(node);
5050
return !!node && kinds.includes(resolved.kind as TKind);

packages/nodes/src/Node.ts

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,6 @@
11
import { CODAMA_ERROR__UNEXPECTED_NODE_KIND, CodamaError } from '@codama/errors';
22
import type { GetNodeFromKind, Node, NodeKind } from '@codama/node-types';
33

4-
import { REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS } from './contextualValueNodes/ContextualValueNode';
5-
import { REGISTERED_COUNT_NODE_KINDS } from './countNodes/CountNode';
6-
import { REGISTERED_DISCRIMINATOR_NODE_KINDS } from './discriminatorNodes/DiscriminatorNode';
7-
import { REGISTERED_LINK_NODE_KINDS } from './linkNodes/LinkNode';
8-
import { REGISTERED_PDA_SEED_NODE_KINDS } from './pdaSeedNodes/PdaSeedNode';
9-
import { REGISTERED_TYPE_NODE_KINDS } from './typeNodes/TypeNode';
10-
import { REGISTERED_VALUE_NODE_KINDS } from './valueNodes/ValueNode';
11-
12-
// Node Registration.
13-
export const REGISTERED_NODE_KINDS = [
14-
...REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS,
15-
...REGISTERED_DISCRIMINATOR_NODE_KINDS,
16-
...REGISTERED_LINK_NODE_KINDS,
17-
...REGISTERED_PDA_SEED_NODE_KINDS,
18-
...REGISTERED_COUNT_NODE_KINDS,
19-
...REGISTERED_TYPE_NODE_KINDS,
20-
...REGISTERED_VALUE_NODE_KINDS,
21-
'rootNode' as const,
22-
'programNode' as const,
23-
'pdaNode' as const,
24-
'accountNode' as const,
25-
'eventNode' as const,
26-
'constantNode' as const,
27-
'instructionAccountNode' as const,
28-
'instructionArgumentNode' as const,
29-
'instructionByteDeltaNode' as const,
30-
'instructionNode' as const,
31-
'instructionRemainingAccountsNode' as const,
32-
'instructionStatusNode' as const,
33-
'errorNode' as const,
34-
'definedTypeNode' as const,
35-
];
36-
37-
// Node Helpers.
38-
394
export function isNode<TKind extends NodeKind>(
405
node: Node | null | undefined,
416
kind: TKind | TKind[],

packages/nodes/src/typeNodes/NumberTypeNode.ts renamed to packages/nodes/src/NumberTypeNode.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,4 @@
1-
import type { NumberFormat, NumberTypeNode } from '@codama/node-types';
2-
3-
export function numberTypeNode<TFormat extends NumberFormat = NumberFormat>(
4-
format: TFormat,
5-
endian: 'be' | 'le' = 'le',
6-
): NumberTypeNode<TFormat> {
7-
return Object.freeze({
8-
kind: 'numberTypeNode',
9-
10-
// Data.
11-
format,
12-
endian,
13-
});
14-
}
1+
import type { NumberTypeNode } from '@codama/node-types';
152

163
export function isSignedInteger(node: NumberTypeNode): boolean {
174
return node.format.startsWith('i');

0 commit comments

Comments
 (0)