Skip to content

Commit e9dfcfa

Browse files
committed
Regenerate the node types from the v2 spec
1 parent 64d52eb commit e9dfcfa

332 files changed

Lines changed: 5558 additions & 2551 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.

‎.changeset/v2-node-types.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@codama/node-types': major
3+
'@codama/nodes': major
4+
'@codama/visitors-core': major
5+
---
6+
7+
Regenerate the node types, constructors and core visitors from the Codama v2 spec (`@codama/spec@2`).
8+
9+
The node model changes substantially from v1:
10+
11+
- **`name` → `identifier`.** Every named node (`programNode`, `accountNode`, `instructionNode`, links, …) now carries `identifier` instead of `name`, and identifiers preserve their casing — v2 no longer mandates camelCase (`transfer_tokens` and `transferTokens` are both valid; uniqueness is resolved by case-folding).
12+
- **Numeric system rework.** `numberTypeNode`/`numberValueNode` split into `integerTypeNode`/`floatTypeNode` and `integerValueNode`/`floatValueNode` (numeric values are string-encoded to stay lossless through JSON). `amountTypeNode`/`solAmountTypeNode` become `fixedPointTypeNode`; new `durationTypeNode`; `dateTimeTypeNode`/`durationTypeNode` carry `ticksPerSecond`.
13+
- **Flat transforms.** The wrapper type nodes (`fixedSizeTypeNode`, `sizePrefixTypeNode`, `pre/postOffsetTypeNode`, `sentinelTypeNode`, `hiddenPrefix/SuffixTypeNode`) are removed; every type node instead carries an optional `transforms: transformNode[]` applied innermost-first.
14+
- **Instruction data.** `instructionArgumentNode`, `instructionArgumentLinkNode`, `resolverValueNode` and `instructionNode.arguments`/`extraArguments` are removed; instruction arguments live in `instructionNode.data`, reached via path expressions. `argumentValueNode` → `dataValueNode`; `accountFieldValueNode` → `accountDataValueNode`.
15+
- **Enum variants unified.** The three variant nodes collapse into a single `enumVariantTypeNode` with an optional `data`.
16+
- **Text and docs.** `docs` and text-bearing attributes are the union `string | textNode`; a `textNode` carries structured metadata (plugins). New `textNode`.
17+
- **Universal plugins.** Every node gains an optional `plugins: pluginNode[]` base attribute — the extension point for renderer-specific or not-yet-standardised metadata.
18+
- **Other.** `programNode.origin` removed (provenance moves to plugins); `pluginNode.name` → `pluginNode.namespace`; `sentinelCountNode` added; path expressions for field references and discriminators.
19+
20+
Node factory ergonomics: trailing optional parameters now live in the constructor's `options` bag (e.g. `constantNode(identifier, type, value, { docs })`, `accountLinkNode(identifier, { program })`).

‎CONTRIBUTING.md‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ pnpm lint # oxlint && oxfmt --check
1313
pnpm lint:fix
1414
```
1515

16-
Parts of the codebase — everything under a `generated/` directory — are produced by the private `@codama-internal/spec-generators` package from the [`@codama/spec`](https://github.com/codama-idl/spec) meta-model, and must never be edited by hand. CI regenerates everything and fails on any diff, so committed generated code always matches the spec pins in `packages/spec-generators/package.json`:
16+
Most `generated/` directories — those under `@codama/node-types`, `@codama/nodes` and `@codama/visitors-core` — are produced by the private `@codama-internal/spec-generators` package from the [`@codama/spec`](https://github.com/codama-idl/spec) meta-model, and must never be edited by hand. CI regenerates them and fails on any diff, so committed generated code always matches the single living spec pin in `packages/spec-generators/package.json`:
1717

18-
- `@codama/spec` is the living pin: the spec version the current major is generated from.
19-
- `@codama/spec-v1` is an aliased pin to the final release of an older major, used solely to refresh the frozen node types committed in `@codama/upgrade`.
18+
- `@codama/spec` is the living pin: the spec version the current major is generated from. It is exact rather than a range, so generated output only changes through a deliberate, reviewable pin (or generator) change.
2019

21-
Both pins are exact rather than ranges, so generated output only changes through a deliberate, reviewable pin (or generator) change.
20+
The generator is **single-major**: it only renders the spec on its own branch. Node types for **older** majors live as frozen static snapshots under `packages/upgrade/src/vN/generated` (e.g. `src/v1/generated`); these are hand-maintained source, are **not** regenerated by `pnpm generate`, and are excluded from the CI freshness check. See `packages/upgrade/src/v1/README.md`.
2221

2322
## Changesets
2423

@@ -28,11 +27,11 @@ Any user-facing change needs a changeset: run `npx changeset add --empty` and ed
2827

2928
Branch, dist-tag and lifecycle mechanics (cut / bake / promote) are defined once for the whole ecosystem in the spec repository's [RELEASING.md](https://github.com/codama-idl/spec/blob/HEAD/RELEASING.md). Releasing major N+1 of the [Codama spec](https://github.com/codama-idl/spec) additionally requires the following steps specific to this repository. The design intent is that this list never grows: one new upgrade function per major, everything else mechanical.
3029

31-
1. **Freeze the vN node types.** In `packages/spec-generators/package.json`, repoint the frozen alias at the final N.x spec release (renaming it `@codama/spec-vN`) and move the living `@codama/spec` pin to the (N+1).x release. Running `pnpm generate` then commits the frozen vN snapshot under `packages/upgrade/src/vN/` and restamps `CODAMA_VERSION` — remember to also freeze copies of the hand-written siblings (`brands.ts`, `Docs.ts`, `Version.ts`) next to the generated output, as done for v1.
32-
2. **Write the upgrade step.** Add a single hand-written, pure `upgradeVNToVN+1` function to `@codama/upgrade` — a JSON-tree-in, JSON-tree-out converter in the `nodes-from-anchor` top-down style — and wire it into `upgrade()` as its `if (major <= N)` block. Export the vN types as a type-only namespace (`export type * as vN`) from the package index.
33-
3. **Run the ecosystem lifecycle.** Cut this repository's `N.x` maintenance branch per RELEASING.md, after which `main` hosts the vN+1 work, with one codama-specific detail: the seeded major changeset covers **all** public packages, upholding the same-major invariant. `main` then versions as `(N+1).0.0-rc.n` under the `rc` dist-tag, bakes under `next`, and is promoted to `latest` while `N.x` switches to `release-N.x`. Old majors receive clarifications and documentation fixes only, never semantic changes.
30+
1. **Freeze the vN node types.** Before moving the living `@codama/spec` pin to the (N+1).x release, snapshot the current vN node types into `@codama/upgrade`: copy `@codama/node-types/src/generated` into `packages/upgrade/src/vN/generated`, keeping the layout identical so a later backported vN change can be ported forward by applying the same patch. Also copy the hand-written siblings (`brands.ts`, `Docs.ts`, `Version.ts`) next to it as vN-shaped frozen copies, so the snapshot is self-contained, and export the vN types as a type-only namespace (`export type * as vN`) from the package index. The snapshot is static source — it is never regenerated. Then move the living `@codama/spec` pin to the (N+1).x release and run `pnpm generate`, which rewrites the living `generated/` dirs and restamps `CODAMA_VERSION`.
31+
2. **Write the upgrade step.** Add a single hand-written, pure `upgradeVNToVN+1` function to `@codama/upgrade` — a JSON-tree-in, JSON-tree-out converter in the `nodes-from-anchor` top-down style — and wire it into `upgrade()` as its `if (major <= N)` block.
32+
3. **Run the ecosystem lifecycle.** Cut this repository's `N.x` maintenance branch per RELEASING.md, after which `main` hosts the vN+1 work, with one codama-specific detail: the seeded major changeset covers **all** public packages, upholding the same-major invariant. `main` then versions as `(N+1).0.0-rc.n` under the `rc` dist-tag through the candidacy, and is promoted to `latest` while `N.x` switches to `release-N.x`. Old majors receive clarifications and documentation fixes only, never semantic changes.
3433

3534
Two invariants protect consumers and must never be broken:
3635

3736
- **The upgrade chain is append-only.** The upgrade functions and frozen node types in `@codama/upgrade` are committed source with zero runtime dependencies; they are never removed or rewritten, so every major back to 1.0.0 stays upgradable forever.
38-
- **Frozen types refresh lazily, generators are never forked.** The generators on `main` only ever consume two spec eras: the living pin and the frozen alias. Keep the alias until the meta-model API drifts incompatibly and `spec-generators` stops compiling — a loud tripwire whose response is to delete the freeze block and the alias (two lines), never to fork era-specific generators. If a backported old-major spec patch matters after that point, generate once on the era's maintenance branch and copy the output forward; otherwise, slightly stale docblocks in frozen types are acceptable, since old majors never change shape.
37+
- **The generator is single-major; snapshots are static.** The generator on `main` only ever renders the living spec of the current major — it is never forked or taught to speak an older spec dialect. Older majors' node types are frozen static snapshots under `packages/upgrade/src/vN/generated`, whose layout mirrors that major's `@codama/node-types/src/generated` so a rare backported change is ported forward as a patch. Slightly stale docblocks in a snapshot are acceptable, since old majors never change shape.

‎packages/errors/src/codes.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND = 9;
3737
export const CODAMA_ERROR__DISCRIMINATOR_FIELD_NOT_FOUND = 10;
3838
export const CODAMA_ERROR__DISCRIMINATOR_FIELD_HAS_NO_DEFAULT_VALUE = 11;
3939
export const CODAMA_ERROR__UNSUPPORTED_VERSION = 12;
40+
export const CODAMA_ERROR__INVALID_BRANDED_STRING = 13;
4041

4142
// Visitors-related errors.
4243
// Reserve error codes in the range [1200000-1200999].
@@ -140,6 +141,7 @@ export type CodamaErrorCode =
140141
| typeof CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE
141142
| typeof CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY
142143
| typeof CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND
144+
| typeof CODAMA_ERROR__INVALID_BRANDED_STRING
143145
| typeof CODAMA_ERROR__LINKED_NODE_NOT_FOUND
144146
| typeof CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE
145147
| typeof CODAMA_ERROR__RENDERERS__MISSING_DEPENDENCY_VERSIONS

‎packages/errors/src/context.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE,
5353
CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY,
5454
CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND,
55+
CODAMA_ERROR__INVALID_BRANDED_STRING,
5556
CODAMA_ERROR__LINKED_NODE_NOT_FOUND,
5657
CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE,
5758
CODAMA_ERROR__RENDERERS__MISSING_DEPENDENCY_VERSIONS,
@@ -204,6 +205,10 @@ export type CodamaErrorContext = DefaultUnspecifiedErrorContextToUndefined<{
204205
enumName: CamelCaseString;
205206
variant: CamelCaseString;
206207
};
208+
[CODAMA_ERROR__INVALID_BRANDED_STRING]: {
209+
actual: string;
210+
expected: string;
211+
};
207212
[CODAMA_ERROR__LINKED_NODE_NOT_FOUND]: {
208213
kind: LinkNode['kind'];
209214
linkNode: LinkNode;

‎packages/errors/src/messages.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_NODE,
3636
CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY,
3737
CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND,
38+
CODAMA_ERROR__INVALID_BRANDED_STRING,
3839
CODAMA_ERROR__LINKED_NODE_NOT_FOUND,
3940
CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE,
4041
CODAMA_ERROR__RENDERERS__MISSING_DEPENDENCY_VERSIONS,
@@ -114,6 +115,7 @@ export const CodamaErrorMessages: Readonly<{
114115
[CODAMA_ERROR__DYNAMIC_CLIENT__UNSUPPORTED_OPTIONAL_ACCOUNT_STRATEGY]:
115116
'Unsupported optional account strategy [$strategy] for account [$accountName] in [$instructionName].',
116117
[CODAMA_ERROR__ENUM_VARIANT_NOT_FOUND]: 'Enum variant [$variant] not found in enum type [$enumName].',
118+
[CODAMA_ERROR__INVALID_BRANDED_STRING]: 'Expected a valid [$expected], got [$actual].',
117119
[CODAMA_ERROR__LINKED_NODE_NOT_FOUND]: 'Could not find linked node [$name] from [$kind].',
118120
[CODAMA_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE]:
119121
'Node.js filesystem function [$fsFunction] is not available in your environment.',

‎packages/node-types/src/Docs.ts‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎packages/node-types/src/brands.ts‎

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,63 @@
11
/**
22
* Hand-written branded string types used throughout the generated
3-
* node-type surface to mark identifiers that must conform to a specific
4-
* casing convention.
3+
* node-type surface to mark strings that must conform to a specific
4+
* spec constraint (identifiers, namespaces, path expressions,
5+
* string-encoded numbers) or casing convention.
56
*
67
* These types live outside `./generated/` because they're static — they
78
* never change with the spec — so there's nothing to regenerate. The
89
* generator's symbol map points at this file when emitting `import type
9-
* { CamelCaseString } from '../brands';` lines.
10+
* { IdentifierString } from '../brands';` lines.
1011
*
1112
* The brand is purely a TypeScript marker; runtime parsing and
12-
* validation happen wherever string identifiers cross the package
13+
* validation happen wherever branded strings cross the package
1314
* boundary.
1415
*/
1516

17+
/**
18+
* A string asserted to be a Codama identifier: `[A-Za-z_][A-Za-z0-9_]*`.
19+
* No casing is mandated — `transferTokens`, `transfer_tokens` and
20+
* `TransferTokens` are all valid — but identifiers sharing a scope must
21+
* remain unique after case-folding and stripping underscores.
22+
*/
23+
export type IdentifierString = string & {
24+
readonly ['__string:codama']: 'identifier';
25+
};
26+
27+
/**
28+
* A string asserted to be a namespace: a chain of identifiers separated
29+
* by single dots (`identifier ("." identifier)*`). Used for plugin
30+
* namespaces.
31+
*/
32+
export type NamespaceString = string & {
33+
readonly ['__string:codama']: 'namespace';
34+
};
35+
36+
/**
37+
* A string asserted to be a path expression pointing into nested data
38+
* (`first ( "." identifier | "[" integer "]" )*`).
39+
*/
40+
export type PathString = string & {
41+
readonly ['__string:codama']: 'path';
42+
};
43+
44+
/**
45+
* A string asserted to be a base-10 integer (`0|-?[1-9][0-9]*`). String
46+
* storage keeps the full 64- and 128-bit ranges lossless through JSON.
47+
*/
48+
export type IntegerString = string & {
49+
readonly ['__string:codama']: 'integer';
50+
};
51+
52+
/**
53+
* A string asserted to be a canonical decimal number
54+
* (`-?(0|[1-9][0-9]*)("." [0-9]*[1-9])?`, or `NaN`/`Infinity`/`-Infinity`).
55+
* String storage makes float round-trips deterministic across serialisers.
56+
*/
57+
export type DecimalString = string & {
58+
readonly ['__string:codama']: 'decimal';
59+
};
60+
1661
/** A string asserted to be in camelCase form. */
1762
export type CamelCaseString = string & {
1863
readonly ['__stringCase:codama']: 'camelCase';
Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,36 @@
1-
import type { CamelCaseString } from '../brands';
2-
import type { Docs } from '../Docs';
1+
import type { IdentifierString } from '../brands';
32
import type { DiscriminatorNode } from './discriminatorNodes/DiscriminatorNode';
43
import type { PdaLinkNode } from './linkNodes/PdaLinkNode';
5-
import type { NestedTypeNode } from './typeNodes/NestedTypeNode';
6-
import type { StructTypeNode } from './typeNodes/StructTypeNode';
4+
import type { PluginNode } from './PluginNode';
5+
import type { TextNode } from './TextNode';
6+
import type { TypeNode } from './typeNodes/TypeNode';
77

88
/**
9-
* An on-chain account: its name, data structure, optional fixed size, optional PDA, and optional discriminators.
9+
* An on-chain account: its identifier, data type, optional fixed size, optional PDA, and optional discriminators.
1010
*
1111
* ![Diagram](https://github.com/codama-idl/codama/assets/3642397/77974dad-212e-49b1-8e41-5d466c273a02)
1212
*/
1313
export interface AccountNode<
14-
TData extends NestedTypeNode<StructTypeNode> = NestedTypeNode<StructTypeNode>,
14+
TDocs extends string | TextNode | undefined = string | TextNode | undefined,
15+
TData extends TypeNode = TypeNode,
1516
TPda extends PdaLinkNode | undefined = PdaLinkNode | undefined,
1617
TDiscriminators extends Array<DiscriminatorNode> | undefined = Array<DiscriminatorNode> | undefined,
18+
TPlugins extends Array<PluginNode> | undefined = Array<PluginNode> | undefined,
1719
> {
1820
readonly kind: 'accountNode';
1921

2022
// Data.
21-
/** The name of the account. */
22-
readonly name: CamelCaseString;
23+
/** The identifier of the account. */
24+
readonly identifier: IdentifierString;
2325
/** The size of the account in bytes, when the data length is fixed. */
2426
readonly size?: number;
25-
/** Markdown documentation for the account. */
26-
readonly docs?: Docs;
2727

2828
// Children.
29+
/** Markdown documentation for the account. */
30+
readonly docs?: TDocs;
2931
/**
30-
* The struct describing the account data.
31-
* It must be a struct so its fields can be referenced by other nodes — e.g. `accountFieldValueNode`.
32+
* The type describing the account data — any type node, including a `definedTypeLinkNode` to share or reuse a defined type.
33+
* Nodes that reference account fields by name — e.g. `accountDataValueNode` or `fieldDiscriminatorNode` — are only valid when this type resolves to a struct (following links).
3234
*/
3335
readonly data: TData;
3436
/** A link to the PDA the account is derived from, if applicable. */
@@ -38,4 +40,9 @@ export interface AccountNode<
3840
* When multiple are listed, they are combined with a logical AND.
3941
*/
4042
readonly discriminators?: TDiscriminators;
43+
/**
44+
* Namespaced plugins with custom structured data.
45+
* The universal extension point for renderer-specific or not-yet-standardised metadata.
46+
*/
47+
readonly plugins?: TPlugins;
4148
}
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
import type { CamelCaseString } from '../brands';
2-
import type { Docs } from '../Docs';
1+
import type { IdentifierString } from '../brands';
2+
import type { PluginNode } from './PluginNode';
3+
import type { TextNode } from './TextNode';
34
import type { TypeNode } from './typeNodes/TypeNode';
45
import type { ValueNode } from './valueNodes/ValueNode';
56

67
/** A named constant exposed by the program: a typed value associated with a name. */
7-
export interface ConstantNode<TType extends TypeNode = TypeNode, TValue extends ValueNode = ValueNode> {
8+
export interface ConstantNode<
9+
TDocs extends string | TextNode | undefined = string | TextNode | undefined,
10+
TType extends TypeNode = TypeNode,
11+
TValue extends ValueNode = ValueNode,
12+
TPlugins extends Array<PluginNode> | undefined = Array<PluginNode> | undefined,
13+
> {
814
readonly kind: 'constantNode';
915

1016
// Data.
11-
/** The name of the constant. */
12-
readonly name: CamelCaseString;
13-
/** Markdown documentation for the constant. */
14-
readonly docs?: Docs;
17+
/** The identifier of the constant. */
18+
readonly identifier: IdentifierString;
1519

1620
// Children.
21+
/** Markdown documentation for the constant. */
22+
readonly docs?: TDocs;
1723
/** The type of the constant. */
1824
readonly type: TType;
1925
/** The concrete value of the constant. */
2026
readonly value: TValue;
27+
/**
28+
* Namespaced plugins with custom structured data.
29+
* The universal extension point for renderer-specific or not-yet-standardised metadata.
30+
*/
31+
readonly plugins?: TPlugins;
2132
}

0 commit comments

Comments
 (0)