Skip to content

Commit be79b8b

Browse files
committed
Bump @codama/spec to 1.6.0-rc.6
Pulls in the camelCase rename of every spec union and enumeration name, the new `{ kind: 'address' }` `TypeExpr` (rendered as plain `string` on the v1 TS surface), and the rc.6 revert of the rc.5 vec-of-children optional flips — leaving `programNode`, `rootNode`, `instructionNode`, `pdaNode`, and `pdaValueNode`'s child arrays required as they were in rc.4. The generator picks up the new `address` kind in both `nodeTypes` and `nodes` `typeExpr` fragments, switches the `registered*` prefix and `UNION_ALIAS_NAMES` keys to camelCase to match the new spec naming, and gains a small constructor-signature fix to avoid emitting invalid `param?: T = default` TypeScript whenever an attribute that's both optional and supplied with a default appears (no such attribute exists in v1 today, but the bug would have surfaced the moment one did). The regenerated `@codama/node-types` surface differs from rc.4 by a single docstring paragraph on `NestedTypeNode` — the spec's nested-union alias name is now camelCase. Test fixtures across the generator's suite are updated to mirror the new convention; runtime behaviour and the public TS API are unchanged.
1 parent e9a89fe commit be79b8b

16 files changed

Lines changed: 114 additions & 91 deletions

File tree

.changeset/wise-otters-hum.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@codama/node-types': patch
3+
---
4+
5+
Bump `@codama/spec` from `1.6.0-rc.4` to `1.6.0-rc.6`. The encoded surface in `@codama/node-types` is functionally unchanged; one docstring paragraph on `NestedTypeNode` now reads `nestedTypeNode<T>` instead of `NestedTypeNode<T>` to mirror the spec's new camelCase nested-union alias name.
6+
7+
Behind the scenes, `@codama-internal/spec-generators` learns about the new `{ kind: 'address' }` `TypeExpr` (rendered as plain `string` on the v1 TS surface — a dedicated `Address` brand may follow in a future spec major), the camelCase rename of every union and enumeration name on the spec side (the generated PascalCase TS identifiers are unaffected since the generator runs each name through `pascalCase()` at render time), and a constructor-signature bug where an attribute that was both `optional` and supplied with a default would emit invalid TS (`param?: T = default`). The bug never triggered against the rc.4 v1 spec but would have surfaced once any future attribute combined `optional: true` with a configured default; the fix is to omit the `?` mark whenever an initializer is present.

packages/node-types/src/generated/typeNodes/NestedTypeNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { TypeNode } from './TypeNode';
99

1010
/**
1111
* A type, possibly wrapped in zero-or-more size, offset, sentinel, or hidden prefix/suffix modifiers.
12-
* The wrapping is recursive: each modifier wraps another `NestedTypeNode<T>` until the inner `T` is reached.
12+
* The wrapping is recursive: each modifier wraps another `nestedTypeNode<T>` until the inner `T` is reached.
1313
*/
1414
export type NestedTypeNode<TType extends TypeNode> =
1515
| FixedSizeTypeNode<NestedTypeNode<TType>>

packages/spec-generators/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
},
1616
"dependencies": {
1717
"@codama/fragments": "workspace:*",
18-
"@codama/spec": "1.6.0-rc.4"
18+
"@codama/spec": "1.6.0-rc.6"
1919
},
2020
"devDependencies": {
2121
"@types/node": "^25",

packages/spec-generators/src/nodeTypes/fragments/typeExpr.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { TypeExpr } from '@codama/spec';
33

44
export function getTypeExprFragment(expr: TypeExpr): Fragment {
55
switch (expr.kind) {
6+
case 'address':
7+
return fragment`string`;
68
case 'string':
79
return getStringExprFragment(expr);
810
case 'integer':

packages/spec-generators/src/nodes/fragments/nodeFunctionPositionalArguments.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,13 @@ function renderParamForAttribute(
6161
const override = config.attributes?.[attr.name];
6262
const paramName = paramIdentifier(attr, override);
6363
const baseTsType = renderParamTsType(attr, typeParameterAttribute, override);
64-
const optionalMark = attr.optional ? '?' : '';
6564
const defaultClause = renderParamDefaultClause(override, typeParameterAttribute);
65+
// `param?: T = default` is a syntax error; an initializer already
66+
// makes the parameter callable without an argument. Emit `?` only
67+
// when the spec marks the attribute optional AND the config supplies
68+
// no default value.
69+
const hasDefault = defaultClause.content !== '';
70+
const optionalMark = attr.optional && !hasDefault ? '?' : '';
6671
return fragment`${paramName}${optionalMark}: ${baseTsType}${defaultClause}`;
6772
}
6873

packages/spec-generators/src/nodes/fragments/typeExpr.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const NODE_TYPES_PACKAGE = '@codama/node-types';
1515

1616
export function getTypeExprFragment(expr: TypeExpr): Fragment {
1717
switch (expr.kind) {
18+
case 'address':
19+
return fragment`string`;
1820
case 'string':
1921
return getStringExprFragment(expr);
2022
case 'integer':

packages/spec-generators/src/shared/unions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import type { NodeSpec, Spec, UnionSpec } from '@codama/spec';
22

33
/**
4-
* Any union whose name starts with `Registered` is a category-registry
5-
* union (e.g. `RegisteredContextualValueNode`). Derived from the spec
4+
* Any union whose name starts with `registered` is a category-registry
5+
* union (e.g. `registeredContextualValueNode`). Derived from the spec
66
* so future categories are picked up automatically.
77
*/
8-
const REGISTERED_CATEGORY_UNION_PREFIX = 'Registered';
8+
const REGISTERED_CATEGORY_UNION_PREFIX = 'registered';
99

1010
/**
1111
* Return the spec's per-category registry unions (those whose names
12-
* start with `Registered`), sorted alphabetically by name.
12+
* start with `registered`), sorted alphabetically by name.
1313
*/
1414
export function getRegisteredCategoryUnions(spec: Spec): readonly UnionSpec[] {
1515
return spec.categories

packages/spec-generators/src/visitorsCore/options.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ export {
2020
/**
2121
* Map from a spec union name to the `@codama/nodes` plural-noun alias
2222
* the visitors should reference in `assertIsNode` /
23-
* `removeNullAndAssertIsNodeFilter` calls (e.g. `'TypeNode'` →
23+
* `removeNullAndAssertIsNodeFilter` calls (e.g. `'typeNode'` →
2424
* `'TYPE_NODES'`). Unions absent from this map are expanded inline as
2525
* a literal kind array.
2626
*/
2727
export const UNION_ALIAS_NAMES: ReadonlyMap<string, string> = new Map([
28-
['ContextualValueNode', 'CONTEXTUAL_VALUE_NODES'],
29-
['CountNode', 'COUNT_NODES'],
30-
['DiscriminatorNode', 'DISCRIMINATOR_NODES'],
31-
['EnumVariantTypeNode', 'ENUM_VARIANT_TYPE_NODES'],
32-
['InstructionInputValueNode', 'INSTRUCTION_INPUT_VALUE_NODES'],
33-
['LinkNode', 'LINK_NODES'],
34-
['PdaSeedNode', 'PDA_SEED_NODES'],
35-
['TypeNode', 'TYPE_NODES'],
36-
['ValueNode', 'VALUE_NODES'],
28+
['contextualValueNode', 'CONTEXTUAL_VALUE_NODES'],
29+
['countNode', 'COUNT_NODES'],
30+
['discriminatorNode', 'DISCRIMINATOR_NODES'],
31+
['enumVariantTypeNode', 'ENUM_VARIANT_TYPE_NODES'],
32+
['instructionInputValueNode', 'INSTRUCTION_INPUT_VALUE_NODES'],
33+
['linkNode', 'LINK_NODES'],
34+
['pdaSeedNode', 'PDA_SEED_NODES'],
35+
['typeNode', 'TYPE_NODES'],
36+
['valueNode', 'VALUE_NODES'],
3737
]);
3838

3939
/**

packages/spec-generators/test/nodeTypes/fragments/nodeRegistry.test.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest';
44

55
import { getNodeRegistryFragment } from '../../../src/nodeTypes/fragments/nodeRegistry';
66

7-
// The renderer derives the registered-union list from any `Registered*`
7+
// The renderer derives the registered-union list from any `registered*`
88
// union in the spec. This helper plumbs a minimum but complete spec:
99
// one stub node per registered category, each registered union
1010
// referencing that node, plus extra top-level nodes the caller can
@@ -24,13 +24,13 @@ function buildSpecWithAllRegisteredUnions(extraTopLevelNodes: readonly string[]
2424
defineCategory('topLevel', {
2525
nodes: [...stubKinds, ...extraTopLevelNodes].map(k => defineNode(k, { attributes: [] })),
2626
unions: [
27-
defineUnion('RegisteredContextualValueNode', { members: ['someContextualValueNode'] }),
28-
defineUnion('RegisteredCountNode', { members: ['someCountNode'] }),
29-
defineUnion('RegisteredDiscriminatorNode', { members: ['someDiscriminatorNode'] }),
30-
defineUnion('RegisteredLinkNode', { members: ['someLinkNode'] }),
31-
defineUnion('RegisteredPdaSeedNode', { members: ['somePdaSeedNode'] }),
32-
defineUnion('RegisteredTypeNode', { members: ['someTypeNode'] }),
33-
defineUnion('RegisteredValueNode', { members: ['someValueNode'] }),
27+
defineUnion('registeredContextualValueNode', { members: ['someContextualValueNode'] }),
28+
defineUnion('registeredCountNode', { members: ['someCountNode'] }),
29+
defineUnion('registeredDiscriminatorNode', { members: ['someDiscriminatorNode'] }),
30+
defineUnion('registeredLinkNode', { members: ['someLinkNode'] }),
31+
defineUnion('registeredPdaSeedNode', { members: ['somePdaSeedNode'] }),
32+
defineUnion('registeredTypeNode', { members: ['someTypeNode'] }),
33+
defineUnion('registeredValueNode', { members: ['someValueNode'] }),
3434
],
3535
}),
3636
],
@@ -55,37 +55,37 @@ describe('getNodeRegistryFragment', () => {
5555
expect(out).toContain('export type GetNodeFromKind<TKind extends NodeKind> = Extract<Node, { kind: TKind }>;');
5656
});
5757

58-
it('lists every RegisteredXxxNode union as a Node member', () => {
58+
it('lists every registeredXxxNode union as a Node member', () => {
5959
const result = getNodeRegistryFragment(buildSpecWithAllRegisteredUnions());
6060
const imports = [...result.imports.keys()].sort();
61-
expect(imports).toContain('union:RegisteredContextualValueNode');
62-
expect(imports).toContain('union:RegisteredCountNode');
63-
expect(imports).toContain('union:RegisteredDiscriminatorNode');
64-
expect(imports).toContain('union:RegisteredLinkNode');
65-
expect(imports).toContain('union:RegisteredPdaSeedNode');
66-
expect(imports).toContain('union:RegisteredTypeNode');
67-
expect(imports).toContain('union:RegisteredValueNode');
61+
expect(imports).toContain('union:registeredContextualValueNode');
62+
expect(imports).toContain('union:registeredCountNode');
63+
expect(imports).toContain('union:registeredDiscriminatorNode');
64+
expect(imports).toContain('union:registeredLinkNode');
65+
expect(imports).toContain('union:registeredPdaSeedNode');
66+
expect(imports).toContain('union:registeredTypeNode');
67+
expect(imports).toContain('union:registeredValueNode');
6868
});
6969

7070
it('lists non-registered nodes as direct Node members', () => {
71-
// `accountNode` and `rootNode` aren't inside any RegisteredXxxNode
71+
// `accountNode` and `rootNode` aren't inside any registeredXxxNode
7272
// union, so they should appear as direct members of `Node`.
7373
const result = getNodeRegistryFragment(buildSpecWithAllRegisteredUnions(['accountNode', 'rootNode']));
7474
const imports = [...result.imports.keys()];
7575
expect(imports).toContain('node:accountNode');
7676
expect(imports).toContain('node:rootNode');
7777
});
7878

79-
it('omits nodes that are reachable through a RegisteredXxxNode union from direct membership', () => {
80-
// `someTypeNode` is covered by `RegisteredTypeNode`; it must not
79+
it('omits nodes that are reachable through a registeredXxxNode union from direct membership', () => {
80+
// `someTypeNode` is covered by `registeredTypeNode`; it must not
8181
// appear as a direct member of `Node`.
8282
const result = getNodeRegistryFragment(buildSpecWithAllRegisteredUnions());
8383
const imports = [...result.imports.keys()];
8484
expect(imports).not.toContain('node:someTypeNode');
8585
});
8686

8787
it('walks nested unions and excludes any node reachable through them from direct membership', () => {
88-
// Nest a sub-union inside `RegisteredTypeNode`. The sub-union's
88+
// Nest a sub-union inside `registeredTypeNode`. The sub-union's
8989
// members must still be considered "covered" and excluded from
9090
// direct `Node` membership.
9191
const spec: Spec = {
@@ -101,15 +101,15 @@ describe('getNodeRegistryFragment', () => {
101101
defineNode('deeplyNestedTypeNode', { attributes: [] }),
102102
],
103103
unions: [
104-
defineUnion('RegisteredContextualValueNode', { members: ['someContextualValueNode'] }),
105-
defineUnion('RegisteredCountNode', { members: ['someCountNode'] }),
106-
defineUnion('RegisteredDiscriminatorNode', { members: ['someDiscriminatorNode'] }),
107-
defineUnion('RegisteredLinkNode', { members: ['someLinkNode'] }),
108-
defineUnion('RegisteredPdaSeedNode', { members: ['somePdaSeedNode'] }),
109-
defineUnion('RegisteredValueNode', { members: ['someValueNode'] }),
110-
defineUnion('InnerTypeNode', { members: ['deeplyNestedTypeNode'] }),
111-
defineUnion('RegisteredTypeNode', {
112-
members: [{ kind: 'union', name: 'InnerTypeNode' }],
104+
defineUnion('registeredContextualValueNode', { members: ['someContextualValueNode'] }),
105+
defineUnion('registeredCountNode', { members: ['someCountNode'] }),
106+
defineUnion('registeredDiscriminatorNode', { members: ['someDiscriminatorNode'] }),
107+
defineUnion('registeredLinkNode', { members: ['someLinkNode'] }),
108+
defineUnion('registeredPdaSeedNode', { members: ['somePdaSeedNode'] }),
109+
defineUnion('registeredValueNode', { members: ['someValueNode'] }),
110+
defineUnion('innerTypeNode', { members: ['deeplyNestedTypeNode'] }),
111+
defineUnion('registeredTypeNode', {
112+
members: [{ kind: 'union', name: 'innerTypeNode' }],
113113
}),
114114
],
115115
}),
@@ -123,23 +123,23 @@ describe('getNodeRegistryFragment', () => {
123123
expect(imports).not.toContain('node:deeplyNestedTypeNode');
124124
});
125125

126-
it('only emits the RegisteredXxxNode unions present in the spec', () => {
126+
it('only emits the registeredXxxNode unions present in the spec', () => {
127127
// The registry is derived from `spec` — unions whose names start
128-
// with `Registered`. If the spec ships only some of them, the
128+
// with `registered`. If the spec ships only some of them, the
129129
// renderer emits exactly those and quietly omits the rest.
130130
const spec: Spec = {
131131
categories: [
132132
defineCategory('topLevel', {
133133
nodes: [defineNode('someContextualValueNode', { attributes: [] })],
134-
unions: [defineUnion('RegisteredContextualValueNode', { members: ['someContextualValueNode'] })],
134+
unions: [defineUnion('registeredContextualValueNode', { members: ['someContextualValueNode'] })],
135135
}),
136136
],
137137
version: '1.0.0',
138138
};
139139
const result = getNodeRegistryFragment(spec);
140140
const imports = [...result.imports.keys()];
141-
expect(imports).toContain('union:RegisteredContextualValueNode');
142-
expect(imports).not.toContain('union:RegisteredCountNode');
141+
expect(imports).toContain('union:registeredContextualValueNode');
142+
expect(imports).not.toContain('union:registeredCountNode');
143143
});
144144

145145
it('sorts the Node members alphabetically for stable output', () => {

packages/spec-generators/test/nodeTypes/fragments/typeExpr.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
address,
23
array,
34
boolean,
45
codamaVersion,
@@ -23,6 +24,12 @@ describe('getTypeExprFragment', () => {
2324
expect(getTypeExprFragment(string()).content).toBe('string');
2425
});
2526

27+
it('renders address as plain string for v1 (no brand, no import)', () => {
28+
const result = getTypeExprFragment(address());
29+
expect(result.content).toBe('string');
30+
expect(result.imports.size).toBe(0);
31+
});
32+
2633
it('renders integer as number', () => {
2734
expect(getTypeExprFragment(u32()).content).toBe('number');
2835
});

0 commit comments

Comments
 (0)