From a31088f95b70e154d11adf2b0a583ed65571dd78 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 27 Nov 2025 18:21:52 +0530 Subject: [PATCH 01/10] feat: add status and statusMessage to InstructionNode --- packages/node-types/src/InstructionNode.ts | 4 +- packages/node-types/src/shared/index.ts | 1 + .../src/shared/instructionStatus.ts | 14 ++++ packages/nodes/docs/InstructionNode.md | 65 +++++++++++++++-- packages/nodes/src/InstructionNode.ts | 2 + packages/nodes/test/InstructionNode.test.ts | 69 +++++++++++++++++++ 6 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 packages/node-types/src/shared/instructionStatus.ts diff --git a/packages/node-types/src/InstructionNode.ts b/packages/node-types/src/InstructionNode.ts index 85fc6e531..3cd2ff234 100644 --- a/packages/node-types/src/InstructionNode.ts +++ b/packages/node-types/src/InstructionNode.ts @@ -3,7 +3,7 @@ import type { InstructionAccountNode } from './InstructionAccountNode'; import type { InstructionArgumentNode } from './InstructionArgumentNode'; import type { InstructionByteDeltaNode } from './InstructionByteDeltaNode'; import type { InstructionRemainingAccountsNode } from './InstructionRemainingAccountsNode'; -import type { CamelCaseString, Docs } from './shared'; +import type { CamelCaseString, Docs, InstructionStatus } from './shared'; type SubInstructionNode = InstructionNode; @@ -26,6 +26,8 @@ export interface InstructionNode< readonly name: CamelCaseString; readonly docs?: Docs; readonly optionalAccountStrategy?: OptionalAccountStrategy; + readonly status?: InstructionStatus; + readonly statusMessage?: string; // Children. readonly accounts: TAccounts; diff --git a/packages/node-types/src/shared/index.ts b/packages/node-types/src/shared/index.ts index 74c22233d..6a2485661 100644 --- a/packages/node-types/src/shared/index.ts +++ b/packages/node-types/src/shared/index.ts @@ -1,4 +1,5 @@ export * from './bytesEncoding'; export * from './docs'; +export * from './instructionStatus'; export * from './stringCases'; export * from './version'; diff --git a/packages/node-types/src/shared/instructionStatus.ts b/packages/node-types/src/shared/instructionStatus.ts new file mode 100644 index 000000000..c2a709392 --- /dev/null +++ b/packages/node-types/src/shared/instructionStatus.ts @@ -0,0 +1,14 @@ +/** + * The status of an instruction. + * + * - `live`: The instruction is accessible (the default state). + * - `deprecated`: The instruction is about to be archived. + * - `archived`: The instruction is no longer accessible. Note that this is better + * than simply removing the instruction from the Codama IDL as explorers would + * still need to parse old instructions for the program. + * - `draft`: The instruction is accessible but not fully implemented yet. + * - `unaudited`: The instruction is accessible and fully implemented but no audit + * was performed for it yet. + */ +export type InstructionStatus = 'archived' | 'deprecated' | 'draft' | 'live' | 'unaudited'; + diff --git a/packages/nodes/docs/InstructionNode.md b/packages/nodes/docs/InstructionNode.md index 5a82028f3..34abe333b 100644 --- a/packages/nodes/docs/InstructionNode.md +++ b/packages/nodes/docs/InstructionNode.md @@ -8,12 +8,14 @@ This node represents an instruction in a program. ### Data -| Attribute | Type | Description | -| ------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `kind` | `"instructionNode"` | The node discriminator. | -| `name` | `CamelCaseString` | The name of the instruction. | -| `docs` | `string[]` | Markdown documentation for the instruction. | -| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | +| Attribute | Type | Description | +| ------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"instructionNode"` | The node discriminator. | +| `name` | `CamelCaseString` | The name of the instruction. | +| `docs` | `string[]` | Markdown documentation for the instruction. | +| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | +| `status` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` \| `"unaudited"` | (Optional) The status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet, `"unaudited"` means implemented but not audited. | +| `statusMessage` | `string` | (Optional) Additional information about the current status for program consumers. | ### Children @@ -167,3 +169,54 @@ instructionNode({ ], }); ``` + +### A deprecated instruction + +```ts +instructionNode({ + name: 'oldIncrement', + status: 'deprecated', + statusMessage: 'Use the `increment` instruction instead. This will be removed in v3.0.0.', + accounts: [instructionAccountNode({ name: 'counter', isWritable: true, isSigner: false })], + arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u8') })], +}); +``` + +### An archived instruction + +```ts +instructionNode({ + name: 'legacyTransfer', + status: 'archived', + statusMessage: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.', + accounts: [ + instructionAccountNode({ name: 'source', isWritable: true, isSigner: true }), + instructionAccountNode({ name: 'destination', isWritable: true, isSigner: false }), + ], + arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u64') })], +}); +``` + +### A draft instruction + +```ts +instructionNode({ + name: 'experimentalFeature', + status: 'draft', + statusMessage: 'this instruction is under development and may change.', + accounts: [instructionAccountNode({ name: 'config', isWritable: true, isSigner: true })], + arguments: [], +}); +``` + +### An unaudited instruction + +```ts +instructionNode({ + name: 'newFeature', + status: 'unaudited', + statusMessage: 'this instruction has not been audited yet. Use at your own risk.', + accounts: [instructionAccountNode({ name: 'account', isWritable: true, isSigner: false })], + arguments: [instructionArgumentNode({ name: 'value', type: numberTypeNode('u64') })], +}); +``` diff --git a/packages/nodes/src/InstructionNode.ts b/packages/nodes/src/InstructionNode.ts index eae0a1527..86cb2ca67 100644 --- a/packages/nodes/src/InstructionNode.ts +++ b/packages/nodes/src/InstructionNode.ts @@ -78,6 +78,8 @@ export function instructionNode< name: camelCase(input.name), docs: parseDocs(input.docs), optionalAccountStrategy: parseOptionalAccountStrategy(input.optionalAccountStrategy), + ...(input.status !== undefined && { status: input.status }), + ...(input.statusMessage !== undefined && { statusMessage: input.statusMessage }), // Children. accounts: (input.accounts ?? []) as TAccounts, diff --git a/packages/nodes/test/InstructionNode.test.ts b/packages/nodes/test/InstructionNode.test.ts index d5c97b502..c1ee2e1d4 100644 --- a/packages/nodes/test/InstructionNode.test.ts +++ b/packages/nodes/test/InstructionNode.test.ts @@ -11,3 +11,72 @@ test('it returns a frozen object', () => { const node = instructionNode({ name: 'foo' }); expect(Object.isFrozen(node)).toBe(true); }); + +test('it defaults to no status', () => { + const node = instructionNode({ name: 'foo' }); + expect(node.status).toBeUndefined(); + expect(node.statusMessage).toBeUndefined(); +}); + +test('it can have a live status', () => { + const node = instructionNode({ name: 'foo', status: 'live' }); + expect(node.status).toBe('live'); +}); + +test('it can have a deprecated status', () => { + const node = instructionNode({ + name: 'foo', + status: 'deprecated', + statusMessage: 'Use the newFoo instruction instead.', + }); + expect(node.status).toBe('deprecated'); + expect(node.statusMessage).toBe('Use the newFoo instruction instead.'); +}); + +test('it can have an archived status', () => { + const node = instructionNode({ + name: 'foo', + status: 'archived', + statusMessage: 'This instruction was removed in v2.0.0.', + }); + expect(node.status).toBe('archived'); + expect(node.statusMessage).toBe('This instruction was removed in v2.0.0.'); +}); + +test('it can have a draft status', () => { + const node = instructionNode({ + name: 'foo', + status: 'draft', + statusMessage: 'This instruction is under development.', + }); + expect(node.status).toBe('draft'); + expect(node.statusMessage).toBe('This instruction is under development.'); +}); + +test('it can have an unaudited status', () => { + const node = instructionNode({ + name: 'foo', + status: 'unaudited', + statusMessage: 'This instruction has not been audited yet.', + }); + expect(node.status).toBe('unaudited'); + expect(node.statusMessage).toBe('This instruction has not been audited yet.'); +}); + +test('it can have a status without a statusMessage', () => { + const node = instructionNode({ name: 'foo', status: 'deprecated' }); + expect(node.status).toBe('deprecated'); + expect(node.statusMessage).toBeUndefined(); +}); + +test('it can have a statusMessage without a status', () => { + const node = instructionNode({ name: 'foo', statusMessage: 'Some context' }); + expect(node.status).toBeUndefined(); + expect(node.statusMessage).toBe('Some context'); +}); + +test('it can have an empty statusMessage', () => { + const node = instructionNode({ name: 'foo', status: 'deprecated', statusMessage: '' }); + expect(node.status).toBe('deprecated'); + expect(node.statusMessage).toBe(''); +}); From 4a4547f7c9108a1eda89e0af8a8629163fcbd9b8 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Fri, 28 Nov 2025 17:29:47 +0530 Subject: [PATCH 02/10] feat: integrate InstructionStatusNode into InstructionNode and update related types --- packages/node-types/src/InstructionNode.ts | 6 +- .../node-types/src/InstructionStatusNode.ts | 9 ++ packages/node-types/src/Node.ts | 2 + packages/node-types/src/index.ts | 1 + .../src/shared/instructionStatus.ts | 4 +- packages/nodes/docs/InstructionNode.md | 38 +++----- packages/nodes/docs/InstructionStatusNode.md | 81 +++++++++++++++++ packages/nodes/src/InstructionNode.ts | 1 - packages/nodes/src/InstructionStatusNode.ts | 14 +++ packages/nodes/src/Node.ts | 1 + packages/nodes/src/index.ts | 1 + packages/nodes/test/InstructionNode.test.ts | 87 ++++++++----------- .../src/getDebugStringVisitor.ts | 2 + packages/visitors-core/src/identityVisitor.ts | 12 +++ packages/visitors-core/src/mergeVisitor.ts | 7 ++ .../test/nodes/InstructionNode.test.ts | 17 ++++ 16 files changed, 197 insertions(+), 86 deletions(-) create mode 100644 packages/node-types/src/InstructionStatusNode.ts create mode 100644 packages/nodes/docs/InstructionStatusNode.md create mode 100644 packages/nodes/src/InstructionStatusNode.ts diff --git a/packages/node-types/src/InstructionNode.ts b/packages/node-types/src/InstructionNode.ts index 3cd2ff234..be6479dd1 100644 --- a/packages/node-types/src/InstructionNode.ts +++ b/packages/node-types/src/InstructionNode.ts @@ -3,7 +3,8 @@ import type { InstructionAccountNode } from './InstructionAccountNode'; import type { InstructionArgumentNode } from './InstructionArgumentNode'; import type { InstructionByteDeltaNode } from './InstructionByteDeltaNode'; import type { InstructionRemainingAccountsNode } from './InstructionRemainingAccountsNode'; -import type { CamelCaseString, Docs, InstructionStatus } from './shared'; +import type { InstructionStatusNode } from './InstructionStatusNode'; +import type { CamelCaseString, Docs } from './shared'; type SubInstructionNode = InstructionNode; @@ -26,8 +27,6 @@ export interface InstructionNode< readonly name: CamelCaseString; readonly docs?: Docs; readonly optionalAccountStrategy?: OptionalAccountStrategy; - readonly status?: InstructionStatus; - readonly statusMessage?: string; // Children. readonly accounts: TAccounts; @@ -36,5 +35,6 @@ export interface InstructionNode< readonly remainingAccounts?: TRemainingAccounts; readonly byteDeltas?: TByteDeltas; readonly discriminators?: TDiscriminators; + readonly status?: InstructionStatusNode; readonly subInstructions?: TSubInstructions; } diff --git a/packages/node-types/src/InstructionStatusNode.ts b/packages/node-types/src/InstructionStatusNode.ts new file mode 100644 index 000000000..e24605225 --- /dev/null +++ b/packages/node-types/src/InstructionStatusNode.ts @@ -0,0 +1,9 @@ +import type { InstructionStatus } from './shared'; + +export interface InstructionStatusNode { + readonly kind: 'instructionStatusNode'; + + // Data. + readonly status: InstructionStatus; + readonly message?: string; +} diff --git a/packages/node-types/src/Node.ts b/packages/node-types/src/Node.ts index 96ae3a95e..c082abf8b 100644 --- a/packages/node-types/src/Node.ts +++ b/packages/node-types/src/Node.ts @@ -9,6 +9,7 @@ import type { InstructionArgumentNode } from './InstructionArgumentNode'; import type { InstructionByteDeltaNode } from './InstructionByteDeltaNode'; import type { InstructionNode } from './InstructionNode'; import type { InstructionRemainingAccountsNode } from './InstructionRemainingAccountsNode'; +import type { InstructionStatusNode } from './InstructionStatusNode'; import type { RegisteredLinkNode } from './linkNodes/LinkNode'; import type { PdaNode } from './PdaNode'; import type { RegisteredPdaSeedNode } from './pdaSeedNodes/PdaSeedNode'; @@ -28,6 +29,7 @@ export type Node = | InstructionByteDeltaNode | InstructionNode | InstructionRemainingAccountsNode + | InstructionStatusNode | PdaNode | ProgramNode | RegisteredContextualValueNode diff --git a/packages/node-types/src/index.ts b/packages/node-types/src/index.ts index 8c7aaba25..8b4a8d2d6 100644 --- a/packages/node-types/src/index.ts +++ b/packages/node-types/src/index.ts @@ -6,6 +6,7 @@ export * from './InstructionArgumentNode'; export * from './InstructionByteDeltaNode'; export * from './InstructionNode'; export * from './InstructionRemainingAccountsNode'; +export * from './InstructionStatusNode'; export * from './Node'; export * from './PdaNode'; export * from './ProgramNode'; diff --git a/packages/node-types/src/shared/instructionStatus.ts b/packages/node-types/src/shared/instructionStatus.ts index c2a709392..67e6180ed 100644 --- a/packages/node-types/src/shared/instructionStatus.ts +++ b/packages/node-types/src/shared/instructionStatus.ts @@ -7,8 +7,6 @@ * than simply removing the instruction from the Codama IDL as explorers would * still need to parse old instructions for the program. * - `draft`: The instruction is accessible but not fully implemented yet. - * - `unaudited`: The instruction is accessible and fully implemented but no audit - * was performed for it yet. */ -export type InstructionStatus = 'archived' | 'deprecated' | 'draft' | 'live' | 'unaudited'; +export type InstructionStatus = 'archived' | 'deprecated' | 'draft' | 'live'; diff --git a/packages/nodes/docs/InstructionNode.md b/packages/nodes/docs/InstructionNode.md index 34abe333b..abfe0cde8 100644 --- a/packages/nodes/docs/InstructionNode.md +++ b/packages/nodes/docs/InstructionNode.md @@ -8,14 +8,12 @@ This node represents an instruction in a program. ### Data -| Attribute | Type | Description | -| ------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `kind` | `"instructionNode"` | The node discriminator. | -| `name` | `CamelCaseString` | The name of the instruction. | -| `docs` | `string[]` | Markdown documentation for the instruction. | -| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | -| `status` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` \| `"unaudited"` | (Optional) The status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet, `"unaudited"` means implemented but not audited. | -| `statusMessage` | `string` | (Optional) Additional information about the current status for program consumers. | +| Attribute | Type | Description | +| ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"instructionNode"` | The node discriminator. | +| `name` | `CamelCaseString` | The name of the instruction. | +| `docs` | `string[]` | Markdown documentation for the instruction. | +| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | ### Children @@ -27,6 +25,7 @@ This node represents an instruction in a program. | `remainingAccounts` | [`InstructionRemainingAccountsNode`](./InstructionRemainingAccountsNode.md)[] | (Optional) The list of dynamic remaining accounts requirements for the instruction. For instance, an instruction may have a variable number of signers at the end of the accounts list. | | `byteDeltas` | [`InstructionByteDeltaNode`](./InstructionByteDeltaNode.md)[] | (Optional) The list of byte variations that the instruction causes. They should all be added together unless the `subtract` attribute is used. | | `discriminators` | [`DiscriminatorNode`](./DiscriminatorNode.md)[] | (Optional) The nodes that distinguish this instruction from others in the program. If multiple discriminators are provided, they are combined using a logical AND operation. | +| `status` | [`InstructionStatusNode`](./InstructionStatusNode.md) | (Optional) The status of the instruction and an optional message about that status. | | `subInstructions` | [`InstructionNode`](./InstructionNode.md)[] | (Optional) A list of nested instructions should this instruction be split into multiple sub-instructions to define distinct scenarios. | ## Functions @@ -123,7 +122,7 @@ instructionNode({ }); ``` -### An instruction with nested versionned instructions +### An instruction with nested versioned instructions ```ts instructionNode({ @@ -175,8 +174,7 @@ instructionNode({ ```ts instructionNode({ name: 'oldIncrement', - status: 'deprecated', - statusMessage: 'Use the `increment` instruction instead. This will be removed in v3.0.0.', + status: instructionStatus('deprecated', { message: 'Use the `increment` instruction instead. This will be removed in v3.0.0.' }), accounts: [instructionAccountNode({ name: 'counter', isWritable: true, isSigner: false })], arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u8') })], }); @@ -187,8 +185,7 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: 'archived', - statusMessage: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.', + status: instructionStatus('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), accounts: [ instructionAccountNode({ name: 'source', isWritable: true, isSigner: true }), instructionAccountNode({ name: 'destination', isWritable: true, isSigner: false }), @@ -202,21 +199,8 @@ instructionNode({ ```ts instructionNode({ name: 'experimentalFeature', - status: 'draft', - statusMessage: 'this instruction is under development and may change.', + status: instructionStatus('draft', { message: 'This instruction is under development and may change.' }), accounts: [instructionAccountNode({ name: 'config', isWritable: true, isSigner: true })], arguments: [], }); ``` - -### An unaudited instruction - -```ts -instructionNode({ - name: 'newFeature', - status: 'unaudited', - statusMessage: 'this instruction has not been audited yet. Use at your own risk.', - accounts: [instructionAccountNode({ name: 'account', isWritable: true, isSigner: false })], - arguments: [instructionArgumentNode({ name: 'value', type: numberTypeNode('u64') })], -}); -``` diff --git a/packages/nodes/docs/InstructionStatusNode.md b/packages/nodes/docs/InstructionStatusNode.md new file mode 100644 index 000000000..30da5e28d --- /dev/null +++ b/packages/nodes/docs/InstructionStatusNode.md @@ -0,0 +1,81 @@ +# `InstructionStatusNode` + +This node represents the status of an instruction along with an optional message. + +## Attributes + +### Data + +| Attribute | Type | Description | +| --------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"instructionStatusNode"` | The node discriminator. | +| `status` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` | The status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet. | +| `message` | `string` | (Optional) Additional information about the current status for program consumers. | + +## Functions + +### `instructionStatus(status, options)` + +Helper function that creates an `InstructionStatusModeNode` object. + +```ts +const statusNode = instructionStatus('deprecated', { message: 'Use the newInstruction instead' }); +``` + +## Examples + +### A live instruction (no status needed) + +For live instructions, you typically don't need to set a status at all: + +```ts +instructionNode({ + name: 'transfer', + accounts: [...], + arguments: [...], +}); +``` + +### A deprecated instruction + +```ts +instructionNode({ + name: 'oldTransfer', + status: instructionStatus('deprecated', { message: 'Use the `transfer` instruction instead. This will be removed in v3.0.0.' }), + accounts: [...], + arguments: [...], +}); +``` + +### An archived instruction + +```ts +instructionNode({ + name: 'legacyTransfer', + status: instructionStatus('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), + accounts: [...], + arguments: [...], +}); +``` + +### A draft instruction + +```ts +instructionNode({ + name: 'experimentalFeature', + status: instructionStatus('draft', { message: 'This instruction is under development and may change.' }), + accounts: [...], + arguments: [...], +}); +``` + +### Status without a message + +```ts +instructionNode({ + name: 'somedInstruction', + status: instructionStatus('deprecated'), + accounts: [...], + arguments: [...], +}); +``` diff --git a/packages/nodes/src/InstructionNode.ts b/packages/nodes/src/InstructionNode.ts index 86cb2ca67..03d2cfd29 100644 --- a/packages/nodes/src/InstructionNode.ts +++ b/packages/nodes/src/InstructionNode.ts @@ -79,7 +79,6 @@ export function instructionNode< docs: parseDocs(input.docs), optionalAccountStrategy: parseOptionalAccountStrategy(input.optionalAccountStrategy), ...(input.status !== undefined && { status: input.status }), - ...(input.statusMessage !== undefined && { statusMessage: input.statusMessage }), // Children. accounts: (input.accounts ?? []) as TAccounts, diff --git a/packages/nodes/src/InstructionStatusNode.ts b/packages/nodes/src/InstructionStatusNode.ts new file mode 100644 index 000000000..5a3264316 --- /dev/null +++ b/packages/nodes/src/InstructionStatusNode.ts @@ -0,0 +1,14 @@ +import type { InstructionStatusNode, InstructionStatus } from '@codama/node-types'; + +export function instructionStatus( + status: InstructionStatus, + options: { message?: string } = {}, +): InstructionStatusNode { + return Object.freeze({ + kind: 'instructionStatusNode', + + // Data. + status, + ...(options.message !== undefined && { message: options.message }), + }); +} diff --git a/packages/nodes/src/Node.ts b/packages/nodes/src/Node.ts index 193787cc0..73b2a7148 100644 --- a/packages/nodes/src/Node.ts +++ b/packages/nodes/src/Node.ts @@ -27,6 +27,7 @@ export const REGISTERED_NODE_KINDS = [ 'instructionByteDeltaNode' as const, 'instructionNode' as const, 'instructionRemainingAccountsNode' as const, + 'instructionStatusNode' as const, 'errorNode' as const, 'definedTypeNode' as const, ]; diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index 70aecdaf9..39ffa8649 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -18,6 +18,7 @@ export * from './InstructionArgumentNode'; export * from './InstructionByteDeltaNode'; export * from './InstructionNode'; export * from './InstructionRemainingAccountsNode'; +export * from './InstructionStatusNode'; export * from './Node'; export * from './PdaNode'; export * from './ProgramNode'; diff --git a/packages/nodes/test/InstructionNode.test.ts b/packages/nodes/test/InstructionNode.test.ts index c1ee2e1d4..9c77bbf8d 100644 --- a/packages/nodes/test/InstructionNode.test.ts +++ b/packages/nodes/test/InstructionNode.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; -import { instructionNode } from '../src'; +import { instructionNode, instructionStatus } from '../src'; test('it returns the right node kind', () => { const node = instructionNode({ name: 'foo' }); @@ -15,68 +15,51 @@ test('it returns a frozen object', () => { test('it defaults to no status', () => { const node = instructionNode({ name: 'foo' }); expect(node.status).toBeUndefined(); - expect(node.statusMessage).toBeUndefined(); }); test('it can have a live status', () => { - const node = instructionNode({ name: 'foo', status: 'live' }); - expect(node.status).toBe('live'); + const statusMode = instructionStatus('live'); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('live'); }); -test('it can have a deprecated status', () => { - const node = instructionNode({ - name: 'foo', - status: 'deprecated', - statusMessage: 'Use the newFoo instruction instead.', - }); - expect(node.status).toBe('deprecated'); - expect(node.statusMessage).toBe('Use the newFoo instruction instead.'); +test('it can have a deprecated status with message', () => { + const statusMode = instructionStatus('deprecated', { message: 'Use the newFoo instruction instead.' }); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('deprecated'); + expect(node.status?.message).toBe('Use the newFoo instruction instead.'); }); -test('it can have an archived status', () => { - const node = instructionNode({ - name: 'foo', - status: 'archived', - statusMessage: 'This instruction was removed in v2.0.0.', - }); - expect(node.status).toBe('archived'); - expect(node.statusMessage).toBe('This instruction was removed in v2.0.0.'); +test('it can have an archived status with message', () => { + const statusMode = instructionStatus('archived', { message: 'This instruction was removed in v2.0.0.' }); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('archived'); + expect(node.status?.message).toBe('This instruction was removed in v2.0.0.'); }); -test('it can have a draft status', () => { - const node = instructionNode({ - name: 'foo', - status: 'draft', - statusMessage: 'This instruction is under development.', - }); - expect(node.status).toBe('draft'); - expect(node.statusMessage).toBe('This instruction is under development.'); +test('it can have a draft status with message', () => { + const statusMode = instructionStatus('draft', { message: 'This instruction is under development.' }); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('draft'); + expect(node.status?.message).toBe('This instruction is under development.'); }); -test('it can have an unaudited status', () => { - const node = instructionNode({ - name: 'foo', - status: 'unaudited', - statusMessage: 'This instruction has not been audited yet.', - }); - expect(node.status).toBe('unaudited'); - expect(node.statusMessage).toBe('This instruction has not been audited yet.'); +test('it can have a status without a message', () => { + const statusMode = instructionStatus('deprecated'); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('deprecated'); + expect(node.status?.message).toBeUndefined(); }); -test('it can have a status without a statusMessage', () => { - const node = instructionNode({ name: 'foo', status: 'deprecated' }); - expect(node.status).toBe('deprecated'); - expect(node.statusMessage).toBeUndefined(); -}); - -test('it can have a statusMessage without a status', () => { - const node = instructionNode({ name: 'foo', statusMessage: 'Some context' }); - expect(node.status).toBeUndefined(); - expect(node.statusMessage).toBe('Some context'); -}); - -test('it can have an empty statusMessage', () => { - const node = instructionNode({ name: 'foo', status: 'deprecated', statusMessage: '' }); - expect(node.status).toBe('deprecated'); - expect(node.statusMessage).toBe(''); +test('it can have an empty message', () => { + const statusMode = instructionStatus('deprecated', { message: '' }); + const node = instructionNode({ name: 'foo', status: statusMode }); + expect(node.status).toBe(statusMode); + expect(node.status?.status).toBe('deprecated'); + expect(node.status?.message).toBe(''); }); diff --git a/packages/visitors-core/src/getDebugStringVisitor.ts b/packages/visitors-core/src/getDebugStringVisitor.ts index f90c377a9..334c81f3f 100644 --- a/packages/visitors-core/src/getDebugStringVisitor.ts +++ b/packages/visitors-core/src/getDebugStringVisitor.ts @@ -61,6 +61,8 @@ function getNodeDetails(node: Node): string[] { ]; case 'instructionByteDeltaNode': return [...(node.subtract ? ['subtract'] : []), ...(node.withHeader ? ['withHeader'] : [])]; + case 'instructionStatusNode': + return [node.status, ...(node.message ? [node.message] : [])]; case 'errorNode': return [node.code.toString(), node.name]; case 'accountLinkNode': diff --git a/packages/visitors-core/src/identityVisitor.ts b/packages/visitors-core/src/identityVisitor.ts index 9bd26f763..1b64c721e 100644 --- a/packages/visitors-core/src/identityVisitor.ts +++ b/packages/visitors-core/src/identityVisitor.ts @@ -34,6 +34,7 @@ import { instructionLinkNode, instructionNode, instructionRemainingAccountsNode, + instructionStatus, mapEntryValueNode, mapTypeNode, mapValueNode, @@ -136,6 +137,8 @@ export function identityVisitor( if (keys.includes('instructionNode')) { visitor.visitInstruction = function visitInstruction(node) { + const status = node.status ? (visit(this)(node.status) ?? undefined) : undefined; + if (status) assertIsNode(status, 'instructionStatusNode'); return instructionNode({ ...node, accounts: node.accounts @@ -162,6 +165,7 @@ export function identityVisitor( .map(visit(this)) .filter(removeNullAndAssertIsNodeFilter('instructionRemainingAccountsNode')) : undefined, + status, subInstructions: node.subInstructions ? node.subInstructions.map(visit(this)).filter(removeNullAndAssertIsNodeFilter('instructionNode')) : undefined, @@ -206,6 +210,14 @@ export function identityVisitor( }; } + if (keys.includes('instructionStatusNode')) { + visitor.visitInstructionStatus = function visitInstructionStatus(node) { + return instructionStatus(node.status, { + ...(node.message !== undefined && { message: node.message }), + }); + }; + } + if (keys.includes('definedTypeNode')) { visitor.visitDefinedType = function visitDefinedType(node) { const type = visit(this)(node.type); diff --git a/packages/visitors-core/src/mergeVisitor.ts b/packages/visitors-core/src/mergeVisitor.ts index 58bf7391c..ca8999ba0 100644 --- a/packages/visitors-core/src/mergeVisitor.ts +++ b/packages/visitors-core/src/mergeVisitor.ts @@ -52,6 +52,7 @@ export function mergeVisitor( if (keys.includes('instructionNode')) { visitor.visitInstruction = function visitInstruction(node) { return merge(node, [ + ...(node.status ? visit(this)(node.status) : []), ...node.accounts.flatMap(visit(this)), ...node.arguments.flatMap(visit(this)), ...(node.extraArguments ?? []).flatMap(visit(this)), @@ -90,6 +91,12 @@ export function mergeVisitor( }; } + if (keys.includes('instructionStatusNode')) { + visitor.visitInstructionStatus = function visitInstructionStatus(node) { + return merge(node, []); + }; + } + if (keys.includes('definedTypeNode')) { visitor.visitDefinedType = function visitDefinedType(node) { return merge(node, visit(this)(node.type)); diff --git a/packages/visitors-core/test/nodes/InstructionNode.test.ts b/packages/visitors-core/test/nodes/InstructionNode.test.ts index 60ee83359..7d9890715 100644 --- a/packages/visitors-core/test/nodes/InstructionNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionNode.test.ts @@ -5,6 +5,7 @@ import { instructionByteDeltaNode, instructionNode, instructionRemainingAccountsNode, + instructionStatus, numberTypeNode, numberValueNode, publicKeyTypeNode, @@ -123,3 +124,19 @@ test('sub instructions', () => { expectMergeVisitorCount(nodeWithSubInstructions, 3); expectIdentityVisitor(nodeWithSubInstructions); }); + +test('status mode', () => { + const nodeWithStatus = instructionNode({ + name: 'deprecatedInstruction', + status: instructionStatus('deprecated', { message: 'Use newInstruction instead' }), + }); + + expectMergeVisitorCount(nodeWithStatus, 2); + expectIdentityVisitor(nodeWithStatus); + expectDebugStringVisitor( + nodeWithStatus, + ` +instructionNode [deprecatedInstruction] +| instructionStatusNode [deprecated.Use newInstruction instead]`, + ); +}); From bcdcdb7fc5d36fc140245b06e102a01b045a7352 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Fri, 28 Nov 2025 17:41:46 +0530 Subject: [PATCH 03/10] fix: correct function name in InstructionStatusNode documentation --- packages/nodes/docs/InstructionStatusNode.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes/docs/InstructionStatusNode.md b/packages/nodes/docs/InstructionStatusNode.md index 30da5e28d..3509fe305 100644 --- a/packages/nodes/docs/InstructionStatusNode.md +++ b/packages/nodes/docs/InstructionStatusNode.md @@ -16,7 +16,7 @@ This node represents the status of an instruction along with an optional message ### `instructionStatus(status, options)` -Helper function that creates an `InstructionStatusModeNode` object. +Helper function that creates an `InstructionStatusNode` object. ```ts const statusNode = instructionStatus('deprecated', { message: 'Use the newInstruction instead' }); From bcaa5c619c412c4812304771068193521c6f2788 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Fri, 28 Nov 2025 17:53:45 +0530 Subject: [PATCH 04/10] refactor: rename instructionStatus to instructionStatusNode in documentation and tests --- packages/nodes/docs/InstructionNode.md | 6 ++--- packages/nodes/docs/InstructionStatusNode.md | 14 +++++------ packages/nodes/src/InstructionStatusNode.ts | 2 +- packages/nodes/test/InstructionNode.test.ts | 14 +++++------ .../nodes/test/InstructionStatusNode.test.ts | 25 +++++++++++++++++++ packages/visitors-core/src/identityVisitor.ts | 4 +-- .../test/nodes/InstructionNode.test.ts | 4 +-- 7 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 packages/nodes/test/InstructionStatusNode.test.ts diff --git a/packages/nodes/docs/InstructionNode.md b/packages/nodes/docs/InstructionNode.md index abfe0cde8..54ccb9294 100644 --- a/packages/nodes/docs/InstructionNode.md +++ b/packages/nodes/docs/InstructionNode.md @@ -174,7 +174,7 @@ instructionNode({ ```ts instructionNode({ name: 'oldIncrement', - status: instructionStatus('deprecated', { message: 'Use the `increment` instruction instead. This will be removed in v3.0.0.' }), + status: instructionStatusNode('deprecated', { message: 'Use the `increment` instruction instead. This will be removed in v3.0.0.' }), accounts: [instructionAccountNode({ name: 'counter', isWritable: true, isSigner: false })], arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u8') })], }); @@ -185,7 +185,7 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: instructionStatus('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), + status: instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), accounts: [ instructionAccountNode({ name: 'source', isWritable: true, isSigner: true }), instructionAccountNode({ name: 'destination', isWritable: true, isSigner: false }), @@ -199,7 +199,7 @@ instructionNode({ ```ts instructionNode({ name: 'experimentalFeature', - status: instructionStatus('draft', { message: 'This instruction is under development and may change.' }), + status: instructionStatusNode('draft', { message: 'This instruction is under development and may change.' }), accounts: [instructionAccountNode({ name: 'config', isWritable: true, isSigner: true })], arguments: [], }); diff --git a/packages/nodes/docs/InstructionStatusNode.md b/packages/nodes/docs/InstructionStatusNode.md index 3509fe305..80c60e818 100644 --- a/packages/nodes/docs/InstructionStatusNode.md +++ b/packages/nodes/docs/InstructionStatusNode.md @@ -14,12 +14,12 @@ This node represents the status of an instruction along with an optional message ## Functions -### `instructionStatus(status, options)` +### `instructionStatusNode(status, options)` Helper function that creates an `InstructionStatusNode` object. ```ts -const statusNode = instructionStatus('deprecated', { message: 'Use the newInstruction instead' }); +const statusNode = instructionStatusNode('deprecated', { message: 'Use the newInstruction instead' }); ``` ## Examples @@ -41,7 +41,7 @@ instructionNode({ ```ts instructionNode({ name: 'oldTransfer', - status: instructionStatus('deprecated', { message: 'Use the `transfer` instruction instead. This will be removed in v3.0.0.' }), + status: instructionStatusNode('deprecated', { message: 'Use the `transfer` instruction instead. This will be removed in v3.0.0.' }), accounts: [...], arguments: [...], }); @@ -52,7 +52,7 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: instructionStatus('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), + status: instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), accounts: [...], arguments: [...], }); @@ -63,7 +63,7 @@ instructionNode({ ```ts instructionNode({ name: 'experimentalFeature', - status: instructionStatus('draft', { message: 'This instruction is under development and may change.' }), + status: instructionStatusNode('draft', { message: 'This instruction is under development and may change.' }), accounts: [...], arguments: [...], }); @@ -73,8 +73,8 @@ instructionNode({ ```ts instructionNode({ - name: 'somedInstruction', - status: instructionStatus('deprecated'), + name: 'someInstruction', + status: instructionStatusNode('deprecated'), accounts: [...], arguments: [...], }); diff --git a/packages/nodes/src/InstructionStatusNode.ts b/packages/nodes/src/InstructionStatusNode.ts index 5a3264316..428779657 100644 --- a/packages/nodes/src/InstructionStatusNode.ts +++ b/packages/nodes/src/InstructionStatusNode.ts @@ -1,6 +1,6 @@ import type { InstructionStatusNode, InstructionStatus } from '@codama/node-types'; -export function instructionStatus( +export function instructionStatusNode( status: InstructionStatus, options: { message?: string } = {}, ): InstructionStatusNode { diff --git a/packages/nodes/test/InstructionNode.test.ts b/packages/nodes/test/InstructionNode.test.ts index 9c77bbf8d..e26757f53 100644 --- a/packages/nodes/test/InstructionNode.test.ts +++ b/packages/nodes/test/InstructionNode.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; -import { instructionNode, instructionStatus } from '../src'; +import { instructionNode, instructionStatusNode } from '../src'; test('it returns the right node kind', () => { const node = instructionNode({ name: 'foo' }); @@ -18,14 +18,14 @@ test('it defaults to no status', () => { }); test('it can have a live status', () => { - const statusMode = instructionStatus('live'); + const statusMode = instructionStatusNode('live'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('live'); }); test('it can have a deprecated status with message', () => { - const statusMode = instructionStatus('deprecated', { message: 'Use the newFoo instruction instead.' }); + const statusMode = instructionStatusNode('deprecated', { message: 'Use the newFoo instruction instead.' }); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('deprecated'); @@ -33,7 +33,7 @@ test('it can have a deprecated status with message', () => { }); test('it can have an archived status with message', () => { - const statusMode = instructionStatus('archived', { message: 'This instruction was removed in v2.0.0.' }); + const statusMode = instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0.' }); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('archived'); @@ -41,7 +41,7 @@ test('it can have an archived status with message', () => { }); test('it can have a draft status with message', () => { - const statusMode = instructionStatus('draft', { message: 'This instruction is under development.' }); + const statusMode = instructionStatusNode('draft', { message: 'This instruction is under development.' }); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('draft'); @@ -49,7 +49,7 @@ test('it can have a draft status with message', () => { }); test('it can have a status without a message', () => { - const statusMode = instructionStatus('deprecated'); + const statusMode = instructionStatusNode('deprecated'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('deprecated'); @@ -57,7 +57,7 @@ test('it can have a status without a message', () => { }); test('it can have an empty message', () => { - const statusMode = instructionStatus('deprecated', { message: '' }); + const statusMode = instructionStatusNode('deprecated', { message: '' }); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); expect(node.status?.status).toBe('deprecated'); diff --git a/packages/nodes/test/InstructionStatusNode.test.ts b/packages/nodes/test/InstructionStatusNode.test.ts new file mode 100644 index 000000000..da42bbf9d --- /dev/null +++ b/packages/nodes/test/InstructionStatusNode.test.ts @@ -0,0 +1,25 @@ +import { expect, test } from 'vitest'; + +import { instructionStatusNode } from '../src'; + +test('it returns the right node kind', () => { + const node = instructionStatusNode('live'); + expect(node.kind).toBe('instructionStatusNode'); +}); + +test('it returns a frozen object', () => { + const node = instructionStatusNode('live'); + expect(Object.isFrozen(node)).toBe(true); +}); + +test('it can have a status with message', () => { + const node = instructionStatusNode('deprecated', { message: 'Use newInstruction' }); + expect(node.status).toBe('deprecated'); + expect(node.message).toBe('Use newInstruction'); +}); + +test('it can have a status without message', () => { + const node = instructionStatusNode('archived'); + expect(node.status).toBe('archived'); + expect(node.message).toBeUndefined(); +}); diff --git a/packages/visitors-core/src/identityVisitor.ts b/packages/visitors-core/src/identityVisitor.ts index 1b64c721e..6ea88d4fd 100644 --- a/packages/visitors-core/src/identityVisitor.ts +++ b/packages/visitors-core/src/identityVisitor.ts @@ -34,7 +34,7 @@ import { instructionLinkNode, instructionNode, instructionRemainingAccountsNode, - instructionStatus, + instructionStatusNode, mapEntryValueNode, mapTypeNode, mapValueNode, @@ -212,7 +212,7 @@ export function identityVisitor( if (keys.includes('instructionStatusNode')) { visitor.visitInstructionStatus = function visitInstructionStatus(node) { - return instructionStatus(node.status, { + return instructionStatusNode(node.status, { ...(node.message !== undefined && { message: node.message }), }); }; diff --git a/packages/visitors-core/test/nodes/InstructionNode.test.ts b/packages/visitors-core/test/nodes/InstructionNode.test.ts index 7d9890715..10e9cd99b 100644 --- a/packages/visitors-core/test/nodes/InstructionNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionNode.test.ts @@ -5,7 +5,7 @@ import { instructionByteDeltaNode, instructionNode, instructionRemainingAccountsNode, - instructionStatus, + instructionStatusNode, numberTypeNode, numberValueNode, publicKeyTypeNode, @@ -128,7 +128,7 @@ test('sub instructions', () => { test('status mode', () => { const nodeWithStatus = instructionNode({ name: 'deprecatedInstruction', - status: instructionStatus('deprecated', { message: 'Use newInstruction instead' }), + status: instructionStatusNode('deprecated', { message: 'Use newInstruction instead' }), }); expectMergeVisitorCount(nodeWithStatus, 2); From 6259c08b8c08adffdcf83b892b6ee72f490628d8 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 17:08:08 +0530 Subject: [PATCH 05/10] rename `InstructionStatusNode.status` to `lifecycle` --- .../node-types/src/InstructionStatusNode.ts | 2 +- packages/nodes/docs/InstructionNode.md | 6 +++--- packages/nodes/docs/InstructionStatusNode.md | 20 +++++++++---------- packages/nodes/src/InstructionStatusNode.ts | 8 ++++---- packages/nodes/test/InstructionNode.test.ts | 20 +++++++++---------- .../nodes/test/InstructionStatusNode.test.ts | 6 +++--- packages/visitors-core/src/identityVisitor.ts | 4 +--- .../test/nodes/InstructionNode.test.ts | 2 +- 8 files changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/node-types/src/InstructionStatusNode.ts b/packages/node-types/src/InstructionStatusNode.ts index e24605225..e7bfb8d10 100644 --- a/packages/node-types/src/InstructionStatusNode.ts +++ b/packages/node-types/src/InstructionStatusNode.ts @@ -4,6 +4,6 @@ export interface InstructionStatusNode { readonly kind: 'instructionStatusNode'; // Data. - readonly status: InstructionStatus; + readonly lifecycle: InstructionStatus; readonly message?: string; } diff --git a/packages/nodes/docs/InstructionNode.md b/packages/nodes/docs/InstructionNode.md index 54ccb9294..bfbd8e12d 100644 --- a/packages/nodes/docs/InstructionNode.md +++ b/packages/nodes/docs/InstructionNode.md @@ -174,7 +174,7 @@ instructionNode({ ```ts instructionNode({ name: 'oldIncrement', - status: instructionStatusNode('deprecated', { message: 'Use the `increment` instruction instead. This will be removed in v3.0.0.' }), + status: instructionStatusNode('deprecated', 'Use the `increment` instruction instead. This will be removed in v3.0.0.'), accounts: [instructionAccountNode({ name: 'counter', isWritable: true, isSigner: false })], arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u8') })], }); @@ -185,7 +185,7 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), + status: instructionStatusNode('archived', 'This instruction was removed in v2.0.0. It is kept here for historical parsing.'), accounts: [ instructionAccountNode({ name: 'source', isWritable: true, isSigner: true }), instructionAccountNode({ name: 'destination', isWritable: true, isSigner: false }), @@ -199,7 +199,7 @@ instructionNode({ ```ts instructionNode({ name: 'experimentalFeature', - status: instructionStatusNode('draft', { message: 'This instruction is under development and may change.' }), + status: instructionStatusNode('draft', 'This instruction is under development and may change.'), accounts: [instructionAccountNode({ name: 'config', isWritable: true, isSigner: true })], arguments: [], }); diff --git a/packages/nodes/docs/InstructionStatusNode.md b/packages/nodes/docs/InstructionStatusNode.md index 80c60e818..3ba36ba45 100644 --- a/packages/nodes/docs/InstructionStatusNode.md +++ b/packages/nodes/docs/InstructionStatusNode.md @@ -6,20 +6,20 @@ This node represents the status of an instruction along with an optional message ### Data -| Attribute | Type | Description | -| --------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `kind` | `"instructionStatusNode"` | The node discriminator. | -| `status` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` | The status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet. | -| `message` | `string` | (Optional) Additional information about the current status for program consumers. | +| Attribute | Type | Description | +| ----------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"instructionStatusNode"` | The node discriminator. | +| `lifecycle` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` | The lifecycle status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet. | +| `message` | `string` | (Optional) Additional information about the current status for program consumers. | ## Functions -### `instructionStatusNode(status, options)` +### `instructionStatusNode(lifecycle, message?)` Helper function that creates an `InstructionStatusNode` object. ```ts -const statusNode = instructionStatusNode('deprecated', { message: 'Use the newInstruction instead' }); +const statusNode = instructionStatusNode('deprecated', 'Use the newInstruction instead'); ``` ## Examples @@ -41,7 +41,7 @@ instructionNode({ ```ts instructionNode({ name: 'oldTransfer', - status: instructionStatusNode('deprecated', { message: 'Use the `transfer` instruction instead. This will be removed in v3.0.0.' }), + status: instructionStatusNode('deprecated', 'Use the `transfer` instruction instead. This will be removed in v3.0.0.'), accounts: [...], arguments: [...], }); @@ -52,7 +52,7 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0. It is kept here for historical parsing.' }), + status: instructionStatusNode('archived', 'This instruction was removed in v2.0.0. It is kept here for historical parsing.'), accounts: [...], arguments: [...], }); @@ -63,7 +63,7 @@ instructionNode({ ```ts instructionNode({ name: 'experimentalFeature', - status: instructionStatusNode('draft', { message: 'This instruction is under development and may change.' }), + status: instructionStatusNode('draft', 'This instruction is under development and may change.'), accounts: [...], arguments: [...], }); diff --git a/packages/nodes/src/InstructionStatusNode.ts b/packages/nodes/src/InstructionStatusNode.ts index 428779657..58ef8b6d4 100644 --- a/packages/nodes/src/InstructionStatusNode.ts +++ b/packages/nodes/src/InstructionStatusNode.ts @@ -1,14 +1,14 @@ import type { InstructionStatusNode, InstructionStatus } from '@codama/node-types'; export function instructionStatusNode( - status: InstructionStatus, - options: { message?: string } = {}, + lifecycle: InstructionStatus, + message?: string, ): InstructionStatusNode { return Object.freeze({ kind: 'instructionStatusNode', // Data. - status, - ...(options.message !== undefined && { message: options.message }), + lifecycle, + ...(message !== undefined && { message }), }); } diff --git a/packages/nodes/test/InstructionNode.test.ts b/packages/nodes/test/InstructionNode.test.ts index e26757f53..a603069d1 100644 --- a/packages/nodes/test/InstructionNode.test.ts +++ b/packages/nodes/test/InstructionNode.test.ts @@ -21,30 +21,30 @@ test('it can have a live status', () => { const statusMode = instructionStatusNode('live'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('live'); + expect(node.status?.lifecycle).toBe('live'); }); test('it can have a deprecated status with message', () => { - const statusMode = instructionStatusNode('deprecated', { message: 'Use the newFoo instruction instead.' }); + const statusMode = instructionStatusNode('deprecated', 'Use the newFoo instruction instead.'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('deprecated'); + expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBe('Use the newFoo instruction instead.'); }); test('it can have an archived status with message', () => { - const statusMode = instructionStatusNode('archived', { message: 'This instruction was removed in v2.0.0.' }); + const statusMode = instructionStatusNode('archived', 'This instruction was removed in v2.0.0.'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('archived'); + expect(node.status?.lifecycle).toBe('archived'); expect(node.status?.message).toBe('This instruction was removed in v2.0.0.'); }); test('it can have a draft status with message', () => { - const statusMode = instructionStatusNode('draft', { message: 'This instruction is under development.' }); + const statusMode = instructionStatusNode('draft', 'This instruction is under development.'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('draft'); + expect(node.status?.lifecycle).toBe('draft'); expect(node.status?.message).toBe('This instruction is under development.'); }); @@ -52,14 +52,14 @@ test('it can have a status without a message', () => { const statusMode = instructionStatusNode('deprecated'); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('deprecated'); + expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBeUndefined(); }); test('it can have an empty message', () => { - const statusMode = instructionStatusNode('deprecated', { message: '' }); + const statusMode = instructionStatusNode('deprecated', ''); const node = instructionNode({ name: 'foo', status: statusMode }); expect(node.status).toBe(statusMode); - expect(node.status?.status).toBe('deprecated'); + expect(node.status?.lifecycle).toBe('deprecated'); expect(node.status?.message).toBe(''); }); diff --git a/packages/nodes/test/InstructionStatusNode.test.ts b/packages/nodes/test/InstructionStatusNode.test.ts index da42bbf9d..e4bf80757 100644 --- a/packages/nodes/test/InstructionStatusNode.test.ts +++ b/packages/nodes/test/InstructionStatusNode.test.ts @@ -13,13 +13,13 @@ test('it returns a frozen object', () => { }); test('it can have a status with message', () => { - const node = instructionStatusNode('deprecated', { message: 'Use newInstruction' }); - expect(node.status).toBe('deprecated'); + const node = instructionStatusNode('deprecated', 'Use newInstruction'); + expect(node.lifecycle).toBe('deprecated'); expect(node.message).toBe('Use newInstruction'); }); test('it can have a status without message', () => { const node = instructionStatusNode('archived'); - expect(node.status).toBe('archived'); + expect(node.lifecycle).toBe('archived'); expect(node.message).toBeUndefined(); }); diff --git a/packages/visitors-core/src/identityVisitor.ts b/packages/visitors-core/src/identityVisitor.ts index 6ea88d4fd..a5928b2af 100644 --- a/packages/visitors-core/src/identityVisitor.ts +++ b/packages/visitors-core/src/identityVisitor.ts @@ -212,9 +212,7 @@ export function identityVisitor( if (keys.includes('instructionStatusNode')) { visitor.visitInstructionStatus = function visitInstructionStatus(node) { - return instructionStatusNode(node.status, { - ...(node.message !== undefined && { message: node.message }), - }); + return instructionStatusNode(node.lifecycle, node.message); }; } diff --git a/packages/visitors-core/test/nodes/InstructionNode.test.ts b/packages/visitors-core/test/nodes/InstructionNode.test.ts index 10e9cd99b..b4d75a68a 100644 --- a/packages/visitors-core/test/nodes/InstructionNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionNode.test.ts @@ -128,7 +128,7 @@ test('sub instructions', () => { test('status mode', () => { const nodeWithStatus = instructionNode({ name: 'deprecatedInstruction', - status: instructionStatusNode('deprecated', { message: 'Use newInstruction instead' }), + status: instructionStatusNode('deprecated', 'Use newInstruction instead'), }); expectMergeVisitorCount(nodeWithStatus, 2); From 842e60dc409a7636f038000c8cba360e6794d7e6 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 17:25:28 +0530 Subject: [PATCH 06/10] refactor: rename InstructionStatusNode field from status to lifecycle and simplify API signature --- .changeset/wet-meals-work.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/wet-meals-work.md diff --git a/.changeset/wet-meals-work.md b/.changeset/wet-meals-work.md new file mode 100644 index 000000000..61616b804 --- /dev/null +++ b/.changeset/wet-meals-work.md @@ -0,0 +1,7 @@ +--- +'@codama/visitors-core': major +'@codama/node-types': major +'@codama/nodes': major +--- + +Rename InstructionStatusNode field from status to lifecycle and simplify API signature From 5be82a28269b00e184cfac24f1426f4774a1b01a Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 19:45:54 +0530 Subject: [PATCH 07/10] refactor: rename `InstructionStatus` to `InstructionLifecycle` --- .changeset/wet-meals-work.md | 6 +++--- packages/node-types/src/InstructionStatusNode.ts | 4 ++-- packages/node-types/src/shared/index.ts | 2 +- .../node-types/src/shared/instructionLifecycle.ts | 3 +++ packages/node-types/src/shared/instructionStatus.ts | 12 ------------ packages/nodes/src/InstructionStatusNode.ts | 4 ++-- packages/visitors-core/src/getDebugStringVisitor.ts | 2 +- 7 files changed, 12 insertions(+), 21 deletions(-) create mode 100644 packages/node-types/src/shared/instructionLifecycle.ts delete mode 100644 packages/node-types/src/shared/instructionStatus.ts diff --git a/.changeset/wet-meals-work.md b/.changeset/wet-meals-work.md index 61616b804..3c6deb5c3 100644 --- a/.changeset/wet-meals-work.md +++ b/.changeset/wet-meals-work.md @@ -1,7 +1,7 @@ --- -'@codama/visitors-core': major -'@codama/node-types': major -'@codama/nodes': major +'@codama/visitors-core': minor +'@codama/node-types': minor +'@codama/nodes': minor --- Rename InstructionStatusNode field from status to lifecycle and simplify API signature diff --git a/packages/node-types/src/InstructionStatusNode.ts b/packages/node-types/src/InstructionStatusNode.ts index e7bfb8d10..3a707c4da 100644 --- a/packages/node-types/src/InstructionStatusNode.ts +++ b/packages/node-types/src/InstructionStatusNode.ts @@ -1,9 +1,9 @@ -import type { InstructionStatus } from './shared'; +import type { InstructionLifecycle } from './shared'; export interface InstructionStatusNode { readonly kind: 'instructionStatusNode'; // Data. - readonly lifecycle: InstructionStatus; + readonly lifecycle: InstructionLifecycle; readonly message?: string; } diff --git a/packages/node-types/src/shared/index.ts b/packages/node-types/src/shared/index.ts index 6a2485661..b2213bb87 100644 --- a/packages/node-types/src/shared/index.ts +++ b/packages/node-types/src/shared/index.ts @@ -1,5 +1,5 @@ export * from './bytesEncoding'; export * from './docs'; -export * from './instructionStatus'; +export * from './instructionLifecycle'; export * from './stringCases'; export * from './version'; diff --git a/packages/node-types/src/shared/instructionLifecycle.ts b/packages/node-types/src/shared/instructionLifecycle.ts new file mode 100644 index 000000000..b1f3efa7b --- /dev/null +++ b/packages/node-types/src/shared/instructionLifecycle.ts @@ -0,0 +1,3 @@ + +export type InstructionLifecycle = 'archived' | 'deprecated' | 'draft' | 'live'; + diff --git a/packages/node-types/src/shared/instructionStatus.ts b/packages/node-types/src/shared/instructionStatus.ts deleted file mode 100644 index 67e6180ed..000000000 --- a/packages/node-types/src/shared/instructionStatus.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * The status of an instruction. - * - * - `live`: The instruction is accessible (the default state). - * - `deprecated`: The instruction is about to be archived. - * - `archived`: The instruction is no longer accessible. Note that this is better - * than simply removing the instruction from the Codama IDL as explorers would - * still need to parse old instructions for the program. - * - `draft`: The instruction is accessible but not fully implemented yet. - */ -export type InstructionStatus = 'archived' | 'deprecated' | 'draft' | 'live'; - diff --git a/packages/nodes/src/InstructionStatusNode.ts b/packages/nodes/src/InstructionStatusNode.ts index 58ef8b6d4..f81d105fe 100644 --- a/packages/nodes/src/InstructionStatusNode.ts +++ b/packages/nodes/src/InstructionStatusNode.ts @@ -1,7 +1,7 @@ -import type { InstructionStatusNode, InstructionStatus } from '@codama/node-types'; +import type { InstructionStatusNode, InstructionLifecycle } from '@codama/node-types'; export function instructionStatusNode( - lifecycle: InstructionStatus, + lifecycle: InstructionLifecycle, message?: string, ): InstructionStatusNode { return Object.freeze({ diff --git a/packages/visitors-core/src/getDebugStringVisitor.ts b/packages/visitors-core/src/getDebugStringVisitor.ts index 334c81f3f..9b971f41a 100644 --- a/packages/visitors-core/src/getDebugStringVisitor.ts +++ b/packages/visitors-core/src/getDebugStringVisitor.ts @@ -62,7 +62,7 @@ function getNodeDetails(node: Node): string[] { case 'instructionByteDeltaNode': return [...(node.subtract ? ['subtract'] : []), ...(node.withHeader ? ['withHeader'] : [])]; case 'instructionStatusNode': - return [node.status, ...(node.message ? [node.message] : [])]; + return [node.lifecycle, ...(node.message ? [node.message] : [])]; case 'errorNode': return [node.code.toString(), node.name]; case 'accountLinkNode': From a237a121976011bc9d21308b0686422f5b37221e Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 19:58:36 +0530 Subject: [PATCH 08/10] remove extraneous empty lines --- packages/node-types/src/shared/instructionLifecycle.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/node-types/src/shared/instructionLifecycle.ts b/packages/node-types/src/shared/instructionLifecycle.ts index b1f3efa7b..6edfd75df 100644 --- a/packages/node-types/src/shared/instructionLifecycle.ts +++ b/packages/node-types/src/shared/instructionLifecycle.ts @@ -1,3 +1 @@ - export type InstructionLifecycle = 'archived' | 'deprecated' | 'draft' | 'live'; - From 044cb9e171990beea3815b2bc3443eb03c418f83 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 20:03:28 +0530 Subject: [PATCH 09/10] fixed style --- packages/nodes/docs/InstructionNode.md | 22 +++++++++++++------- packages/nodes/docs/InstructionStatusNode.md | 8 +++---- packages/nodes/src/InstructionStatusNode.ts | 7 ++----- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/nodes/docs/InstructionNode.md b/packages/nodes/docs/InstructionNode.md index bfbd8e12d..b6a092dec 100644 --- a/packages/nodes/docs/InstructionNode.md +++ b/packages/nodes/docs/InstructionNode.md @@ -9,11 +9,11 @@ This node represents an instruction in a program. ### Data | Attribute | Type | Description | -| ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `kind` | `"instructionNode"` | The node discriminator. | -| `name` | `CamelCaseString` | The name of the instruction. | -| `docs` | `string[]` | Markdown documentation for the instruction. | -| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | +| ------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `kind` | `"instructionNode"` | The node discriminator. | +| `name` | `CamelCaseString` | The name of the instruction. | +| `docs` | `string[]` | Markdown documentation for the instruction. | +| `optionalAccountStrategy` | `"omitted"` \| `"programId"` | (Optional) Determines how to handle optional accounts. `"omitted"` means optional accounts that are not provided will be omitted from the list of accounts, `"programId"` means they will be replaced by the address of the program to ensure account ordering with only 1 byte of overhead. Defaults to `"programId"`. | ### Children @@ -25,7 +25,7 @@ This node represents an instruction in a program. | `remainingAccounts` | [`InstructionRemainingAccountsNode`](./InstructionRemainingAccountsNode.md)[] | (Optional) The list of dynamic remaining accounts requirements for the instruction. For instance, an instruction may have a variable number of signers at the end of the accounts list. | | `byteDeltas` | [`InstructionByteDeltaNode`](./InstructionByteDeltaNode.md)[] | (Optional) The list of byte variations that the instruction causes. They should all be added together unless the `subtract` attribute is used. | | `discriminators` | [`DiscriminatorNode`](./DiscriminatorNode.md)[] | (Optional) The nodes that distinguish this instruction from others in the program. If multiple discriminators are provided, they are combined using a logical AND operation. | -| `status` | [`InstructionStatusNode`](./InstructionStatusNode.md) | (Optional) The status of the instruction and an optional message about that status. | +| `status` | [`InstructionStatusNode`](./InstructionStatusNode.md) | (Optional) The status of the instruction and an optional message about that status. | | `subInstructions` | [`InstructionNode`](./InstructionNode.md)[] | (Optional) A list of nested instructions should this instruction be split into multiple sub-instructions to define distinct scenarios. | ## Functions @@ -174,7 +174,10 @@ instructionNode({ ```ts instructionNode({ name: 'oldIncrement', - status: instructionStatusNode('deprecated', 'Use the `increment` instruction instead. This will be removed in v3.0.0.'), + status: instructionStatusNode( + 'deprecated', + 'Use the `increment` instruction instead. This will be removed in v3.0.0.', + ), accounts: [instructionAccountNode({ name: 'counter', isWritable: true, isSigner: false })], arguments: [instructionArgumentNode({ name: 'amount', type: numberTypeNode('u8') })], }); @@ -185,7 +188,10 @@ instructionNode({ ```ts instructionNode({ name: 'legacyTransfer', - status: instructionStatusNode('archived', 'This instruction was removed in v2.0.0. It is kept here for historical parsing.'), + status: instructionStatusNode( + 'archived', + 'This instruction was removed in v2.0.0. It is kept here for historical parsing.', + ), accounts: [ instructionAccountNode({ name: 'source', isWritable: true, isSigner: true }), instructionAccountNode({ name: 'destination', isWritable: true, isSigner: false }), diff --git a/packages/nodes/docs/InstructionStatusNode.md b/packages/nodes/docs/InstructionStatusNode.md index 3ba36ba45..1ccd7c802 100644 --- a/packages/nodes/docs/InstructionStatusNode.md +++ b/packages/nodes/docs/InstructionStatusNode.md @@ -6,11 +6,11 @@ This node represents the status of an instruction along with an optional message ### Data -| Attribute | Type | Description | -| ----------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `kind` | `"instructionStatusNode"` | The node discriminator. | +| Attribute | Type | Description | +| ----------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `kind` | `"instructionStatusNode"` | The node discriminator. | | `lifecycle` | `"live"` \| `"deprecated"` \| `"archived"` \| `"draft"` | The lifecycle status of the instruction. `"live"` means accessible (the default), `"deprecated"` means about to be archived, `"archived"` means no longer accessible but kept for historical parsing, `"draft"` means not fully implemented yet. | -| `message` | `string` | (Optional) Additional information about the current status for program consumers. | +| `message` | `string` | (Optional) Additional information about the current status for program consumers. | ## Functions diff --git a/packages/nodes/src/InstructionStatusNode.ts b/packages/nodes/src/InstructionStatusNode.ts index f81d105fe..68681068f 100644 --- a/packages/nodes/src/InstructionStatusNode.ts +++ b/packages/nodes/src/InstructionStatusNode.ts @@ -1,9 +1,6 @@ -import type { InstructionStatusNode, InstructionLifecycle } from '@codama/node-types'; +import type { InstructionLifecycle, InstructionStatusNode } from '@codama/node-types'; -export function instructionStatusNode( - lifecycle: InstructionLifecycle, - message?: string, -): InstructionStatusNode { +export function instructionStatusNode(lifecycle: InstructionLifecycle, message?: string): InstructionStatusNode { return Object.freeze({ kind: 'instructionStatusNode', From de9dff3670ba594073411ae92f1600bb81bdd680 Mon Sep 17 00:00:00 2001 From: tanmay4l Date: Thu, 11 Dec 2025 20:06:06 +0530 Subject: [PATCH 10/10] update changeset description --- .changeset/wet-meals-work.md | 2 +- README.md | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.changeset/wet-meals-work.md b/.changeset/wet-meals-work.md index 3c6deb5c3..fa177aa86 100644 --- a/.changeset/wet-meals-work.md +++ b/.changeset/wet-meals-work.md @@ -4,4 +4,4 @@ '@codama/nodes': minor --- -Rename InstructionStatusNode field from status to lifecycle and simplify API signature +Add a new InstructionStatusNode to instructions diff --git a/README.md b/README.md index b95106027..bdc0fcf69 100644 --- a/README.md +++ b/README.md @@ -114,15 +114,14 @@ Feel free to PR your own visitor here for others to discover. Note that they are ### Generates program clients -| Visitor | Description | Maintainer | -| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------- | -| `@codama/renderers-js` ([docs](https://github.com/codama-idl/renderers-js)) | Generates a JavaScript client compatible with [Solana Kit](https://www.solanakit.com/). | [Anza](https://www.anza.xyz/) | -| `@codama/renderers-js-umi` ([docs](https://github.com/codama-idl/renderers-js-umi)) | Generates a JavaScript client compatible with [the Umi framework](https://developers.metaplex.com/umi). | [Metaplex](https://www.metaplex.com/) | -| `@codama/renderers-rust` ([docs](https://github.com/codama-idl/renderers-rust)) | Generates a Rust client compatible with [the Solana SDK](https://github.com/anza-xyz/solana-sdk). | [Anza](https://www.anza.xyz/) | -| `@codama/renderers-vixen-parser` ([docs](https://github.com/codama-idl/renderers-vixen-parser)) | Generates [Yellowstone](https://github.com/rpcpool/yellowstone-grpc) account and instruction parsers. | [Triton One](https://triton.one/) | -| `@limechain/codama-dart` ([docs](https://github.com/limechain/codama-dart))| Generates a Dart client. | [LimeChain](https://github.com/limechain/)| -| `codama-py` ([docs](https://github.com/Solana-ZH/codama-py)) | Generates a Python client. | [Solar](https://github.com/Solana-ZH) | - +| Visitor | Description | Maintainer | +| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `@codama/renderers-js` ([docs](https://github.com/codama-idl/renderers-js)) | Generates a JavaScript client compatible with [Solana Kit](https://www.solanakit.com/). | [Anza](https://www.anza.xyz/) | +| `@codama/renderers-js-umi` ([docs](https://github.com/codama-idl/renderers-js-umi)) | Generates a JavaScript client compatible with [the Umi framework](https://developers.metaplex.com/umi). | [Metaplex](https://www.metaplex.com/) | +| `@codama/renderers-rust` ([docs](https://github.com/codama-idl/renderers-rust)) | Generates a Rust client compatible with [the Solana SDK](https://github.com/anza-xyz/solana-sdk). | [Anza](https://www.anza.xyz/) | +| `@codama/renderers-vixen-parser` ([docs](https://github.com/codama-idl/renderers-vixen-parser)) | Generates [Yellowstone](https://github.com/rpcpool/yellowstone-grpc) account and instruction parsers. | [Triton One](https://triton.one/) | +| `@limechain/codama-dart` ([docs](https://github.com/limechain/codama-dart)) | Generates a Dart client. | [LimeChain](https://github.com/limechain/) | +| `codama-py` ([docs](https://github.com/Solana-ZH/codama-py)) | Generates a Python client. | [Solar](https://github.com/Solana-ZH) | ### Provides utility