Procházet zdrojové kódy

Add nodes package (#3)

Loris Leiva před 1 rokem
rodič
revize
9cf49e380e
100 změnil soubory, kde provedl 1308 přidání a 114 odebrání
  1. 1 1
      packages/internals/ava.config.browser.mjs
  2. 1 1
      packages/internals/ava.config.node.mjs
  3. 1 1
      packages/internals/ava.config.react-native.mjs
  4. 1 5
      packages/library/src/index.ts
  5. 0 5
      packages/library/src/types/global.d.ts
  6. 4 14
      packages/library/test/index.test.ts
  7. 0 5
      packages/library/test/types/global.d.ts
  8. 23 3
      packages/node-types/package.json
  9. 3 3
      packages/node-types/src/AccountNode.ts
  10. 3 3
      packages/node-types/src/DefinedTypeNode.ts
  11. 3 3
      packages/node-types/src/ErrorNode.ts
  12. 3 3
      packages/node-types/src/InstructionAccountNode.ts
  13. 3 3
      packages/node-types/src/InstructionArgumentNode.ts
  14. 3 3
      packages/node-types/src/InstructionNode.ts
  15. 2 1
      packages/node-types/src/InstructionRemainingAccountsNode.ts
  16. 3 2
      packages/node-types/src/PdaNode.ts
  17. 3 3
      packages/node-types/src/ProgramNode.ts
  18. 2 2
      packages/node-types/src/contextualValueNodes/AccountBumpValueNode.ts
  19. 2 2
      packages/node-types/src/contextualValueNodes/AccountValueNode.ts
  20. 2 2
      packages/node-types/src/contextualValueNodes/ArgumentValueNode.ts
  21. 2 3
      packages/node-types/src/contextualValueNodes/ConditionalValueNode.ts
  22. 2 2
      packages/node-types/src/contextualValueNodes/PdaSeedValueNode.ts
  23. 3 3
      packages/node-types/src/contextualValueNodes/ResolverValueNode.ts
  24. 2 2
      packages/node-types/src/discriminatorNodes/FieldDiscriminatorNode.ts
  25. 2 2
      packages/node-types/src/linkNodes/AccountLinkNode.ts
  26. 2 2
      packages/node-types/src/linkNodes/DefinedTypeLinkNode.ts
  27. 2 2
      packages/node-types/src/linkNodes/PdaLinkNode.ts
  28. 2 2
      packages/node-types/src/linkNodes/ProgramLinkNode.ts
  29. 3 3
      packages/node-types/src/pdaSeedNodes/VariablePdaSeedNode.ts
  30. 1 0
      packages/node-types/src/shared/docs.ts
  31. 2 1
      packages/node-types/src/shared/index.ts
  32. 0 3
      packages/node-types/src/shared/mainCase.ts
  33. 19 0
      packages/node-types/src/shared/stringCases.ts
  34. 2 2
      packages/node-types/src/typeNodes/EnumEmptyVariantTypeNode.ts
  35. 2 2
      packages/node-types/src/typeNodes/EnumStructVariantTypeNode.ts
  36. 2 2
      packages/node-types/src/typeNodes/EnumTupleVariantTypeNode.ts
  37. 3 3
      packages/node-types/src/typeNodes/StructFieldTypeNode.ts
  38. 2 2
      packages/node-types/src/valueNodes/EnumValueNode.ts
  39. 2 2
      packages/node-types/src/valueNodes/PublicKeyValueNode.ts
  40. 2 2
      packages/node-types/src/valueNodes/StructFieldValueNode.ts
  41. 6 0
      packages/nodes/.eslintrc.cjs
  42. 45 0
      packages/nodes/src/AccountNode.ts
  43. 29 0
      packages/nodes/src/DefinedTypeNode.ts
  44. 30 0
      packages/nodes/src/ErrorNode.ts
  45. 29 0
      packages/nodes/src/InstructionAccountNode.ts
  46. 51 0
      packages/nodes/src/InstructionArgumentNode.ts
  47. 22 0
      packages/nodes/src/InstructionByteDeltaNode.ts
  48. 116 0
      packages/nodes/src/InstructionNode.ts
  49. 26 0
      packages/nodes/src/InstructionRemainingAccountsNode.ts
  50. 75 0
      packages/nodes/src/Node.ts
  51. 29 0
      packages/nodes/src/PdaNode.ts
  52. 80 0
      packages/nodes/src/ProgramNode.ts
  53. 18 0
      packages/nodes/src/RootNode.ts
  54. 12 0
      packages/nodes/src/contextualValueNodes/AccountBumpValueNode.ts
  55. 12 0
      packages/nodes/src/contextualValueNodes/AccountValueNode.ts
  56. 12 0
      packages/nodes/src/contextualValueNodes/ArgumentValueNode.ts
  57. 32 0
      packages/nodes/src/contextualValueNodes/ConditionalValueNode.ts
  58. 24 0
      packages/nodes/src/contextualValueNodes/ContextualValueNode.ts
  59. 5 0
      packages/nodes/src/contextualValueNodes/IdentityValueNode.ts
  60. 5 0
      packages/nodes/src/contextualValueNodes/PayerValueNode.ts
  61. 17 0
      packages/nodes/src/contextualValueNodes/PdaSeedValueNode.ts
  62. 16 0
      packages/nodes/src/contextualValueNodes/PdaValueNode.ts
  63. 5 0
      packages/nodes/src/contextualValueNodes/ProgramIdValueNode.ts
  64. 24 0
      packages/nodes/src/contextualValueNodes/ResolverValueNode.ts
  65. 11 0
      packages/nodes/src/contextualValueNodes/index.ts
  66. 9 0
      packages/nodes/src/countNodes/CountNode.ts
  67. 10 0
      packages/nodes/src/countNodes/FixedCountNode.ts
  68. 12 0
      packages/nodes/src/countNodes/PrefixedCountNode.ts
  69. 5 0
      packages/nodes/src/countNodes/RemainderCountNode.ts
  70. 4 0
      packages/nodes/src/countNodes/index.ts
  71. 16 0
      packages/nodes/src/discriminatorNodes/ConstantDiscriminatorNode.ts
  72. 9 0
      packages/nodes/src/discriminatorNodes/DiscriminatorNode.ts
  73. 13 0
      packages/nodes/src/discriminatorNodes/FieldDiscriminatorNode.ts
  74. 10 0
      packages/nodes/src/discriminatorNodes/SizeDiscriminatorNode.ts
  75. 4 0
      packages/nodes/src/discriminatorNodes/index.ts
  76. 22 2
      packages/nodes/src/index.ts
  77. 13 0
      packages/nodes/src/linkNodes/AccountLinkNode.ts
  78. 13 0
      packages/nodes/src/linkNodes/DefinedTypeLinkNode.ts
  79. 10 0
      packages/nodes/src/linkNodes/LinkNode.ts
  80. 13 0
      packages/nodes/src/linkNodes/PdaLinkNode.ts
  81. 13 0
      packages/nodes/src/linkNodes/ProgramLinkNode.ts
  82. 5 0
      packages/nodes/src/linkNodes/index.ts
  83. 39 0
      packages/nodes/src/pdaSeedNodes/ConstantPdaSeedNode.ts
  84. 5 0
      packages/nodes/src/pdaSeedNodes/PdaSeedNode.ts
  85. 20 0
      packages/nodes/src/pdaSeedNodes/VariablePdaSeedNode.ts
  86. 3 0
      packages/nodes/src/pdaSeedNodes/index.ts
  87. 0 7
      packages/nodes/src/platform.ts
  88. 8 0
      packages/nodes/src/shared/docs.ts
  89. 2 0
      packages/nodes/src/shared/index.ts
  90. 39 0
      packages/nodes/src/shared/stringCases.ts
  91. 18 0
      packages/nodes/src/typeNodes/AmountTypeNode.ts
  92. 14 0
      packages/nodes/src/typeNodes/ArrayTypeNode.ts
  93. 14 0
      packages/nodes/src/typeNodes/BooleanTypeNode.ts
  94. 5 0
      packages/nodes/src/typeNodes/BytesTypeNode.ts
  95. 12 0
      packages/nodes/src/typeNodes/DateTimeTypeNode.ts
  96. 18 0
      packages/nodes/src/typeNodes/EnumEmptyVariantTypeNode.ts
  97. 25 0
      packages/nodes/src/typeNodes/EnumStructVariantTypeNode.ts
  98. 25 0
      packages/nodes/src/typeNodes/EnumTupleVariantTypeNode.ts
  99. 24 0
      packages/nodes/src/typeNodes/EnumTypeNode.ts
  100. 5 0
      packages/nodes/src/typeNodes/EnumVariantTypeNode.ts

+ 1 - 1
packages/internals/ava.config.browser.mjs

@@ -1,5 +1,5 @@
 export default {
-    files: ['test/**/*.ts'],
+    files: ['test/**/*.test.ts'],
     nodeArguments: ['--conditions', 'browser'],
     typescript: {
         compile: false,

+ 1 - 1
packages/internals/ava.config.node.mjs

@@ -1,5 +1,5 @@
 export default {
-    files: ['test/**/*.ts'],
+    files: ['test/**/*.test.ts'],
     nodeArguments: ['--conditions', 'node'],
     typescript: {
         compile: false,

+ 1 - 1
packages/internals/ava.config.react-native.mjs

@@ -1,5 +1,5 @@
 export default {
-    files: ['test/**/*.ts'],
+    files: ['test/**/*.test.ts'],
     nodeArguments: ['--conditions', 'react-native'],
     typescript: {
         compile: false,

+ 1 - 5
packages/library/src/index.ts

@@ -1,5 +1 @@
-import { getPlatform, getVersion } from '@kinobi-so/nodes';
-
-export function getInfo(): string {
-    return `Platform: ${getPlatform()}, Version: ${getVersion()}`;
-}
+export * from '@kinobi-so/nodes';

+ 0 - 5
packages/library/src/types/global.d.ts

@@ -1,5 +0,0 @@
-declare const __BROWSER__: boolean;
-declare const __DEV__: boolean;
-declare const __NODEJS__: boolean;
-declare const __REACTNATIVE__: boolean;
-declare const __VERSION__: string;

+ 4 - 14
packages/library/test/index.test.ts

@@ -1,17 +1,7 @@
 import test from 'ava';
 
-import { getInfo } from '../src/index.js';
+import { rootNode } from '../src/index.js';
 
-if (__BROWSER__) {
-    test('it returns browser info', t => {
-        t.is(getInfo(), 'Platform: browser, Version: 0.20.0');
-    });
-} else if (__REACTNATIVE__) {
-    test('it returns react native info', t => {
-        t.is(getInfo(), 'Platform: react-native, Version: 0.20.0');
-    });
-} else {
-    test('it returns node info', t => {
-        t.is(getInfo(), 'Platform: node, Version: 0.20.0');
-    });
-}
+test('it exports node helpers', t => {
+    t.is(typeof rootNode, 'function');
+});

+ 0 - 5
packages/library/test/types/global.d.ts

@@ -1,5 +0,0 @@
-declare const __BROWSER__: boolean;
-declare const __DEV__: boolean;
-declare const __NODEJS__: boolean;
-declare const __REACTNATIVE__: boolean;
-declare const __VERSION__: string;

+ 23 - 3
packages/node-types/package.json

@@ -3,11 +3,29 @@
     "version": "0.20.0",
     "description": "Node specifications for the Kinobi standard",
     "exports": {
+        "react-native": "./dist/index.react-native.js",
+        "browser": {
+            "import": "./dist/index.browser.js",
+            "require": "./dist/index.browser.cjs"
+        },
+        "node": {
+            "import": "./dist/index.node.js",
+            "require": "./dist/index.node.cjs"
+        },
         "types": "./dist/types/index.d.ts"
     },
+    "browser": {
+        "./dist/index.node.cjs": "./dist/index.browser.cjs",
+        "./dist/index.node.js": "./dist/index.browser.js"
+    },
+    "main": "./dist/index.node.cjs",
+    "module": "./dist/index.node.js",
+    "react-native": "./dist/index.react-native.js",
     "types": "./dist/types/index.d.ts",
+    "type": "module",
     "files": [
-        "./dist/types"
+        "./dist/types",
+        "./dist/index.*"
     ],
     "sideEffects": false,
     "keywords": [
@@ -17,12 +35,14 @@
         "specifications"
     ],
     "scripts": {
-        "build": "rimraf dist && pnpm build:types",
+        "build": "rimraf dist && pnpm build:src && pnpm build:types",
+        "build:src": "zx ../../node_modules/@kinobi-so/internals/scripts/build-src.mjs package",
         "build:types": "zx ../../node_modules/@kinobi-so/internals/scripts/build-types.mjs",
         "lint": "zx ../../node_modules/@kinobi-so/internals/scripts/lint.mjs",
         "lint:fix": "zx ../../node_modules/@kinobi-so/internals/scripts/lint.mjs --fix",
         "prepublishOnly": "pnpm build",
-        "test": "pnpm test:types",
+        "test": "pnpm test:types && pnpm test:treeshakability",
+        "test:treeshakability": "zx ../../node_modules/@kinobi-so/internals/scripts/test-treeshakability.mjs",
         "test:types": "zx ../../node_modules/@kinobi-so/internals/scripts/test-types.mjs"
     },
     "license": "MIT",

+ 3 - 3
packages/node-types/src/AccountNode.ts

@@ -1,6 +1,6 @@
 import type { DiscriminatorNode } from './discriminatorNodes';
 import type { PdaLinkNode } from './linkNodes';
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 import type { NestedTypeNode, StructTypeNode } from './typeNodes';
 
 export interface AccountNode<
@@ -11,9 +11,9 @@ export interface AccountNode<
     readonly kind: 'accountNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly size?: number | null;
-    readonly docs: string[];
+    readonly docs: Docs;
 
     // Children.
     readonly data: TData;

+ 3 - 3
packages/node-types/src/DefinedTypeNode.ts

@@ -1,12 +1,12 @@
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 import type { TypeNode } from './typeNodes/TypeNode';
 
 export interface DefinedTypeNode<TType extends TypeNode = TypeNode> {
     readonly kind: 'definedTypeNode';
 
     // Data.
-    readonly name: MainCaseString;
-    readonly docs: string[];
+    readonly name: CamelCaseString;
+    readonly docs: Docs;
 
     // Children.
     readonly type: TType;

+ 3 - 3
packages/node-types/src/ErrorNode.ts

@@ -1,11 +1,11 @@
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 
 export interface ErrorNode {
     readonly kind: 'errorNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly code: number;
     readonly message: string;
-    readonly docs: string[];
+    readonly docs: Docs;
 }

+ 3 - 3
packages/node-types/src/InstructionAccountNode.ts

@@ -1,5 +1,5 @@
 import type { InstructionInputValueNode } from './contextualValueNodes';
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 
 export interface InstructionAccountNode<
     TDefaultValue extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
@@ -7,11 +7,11 @@ export interface InstructionAccountNode<
     readonly kind: 'instructionAccountNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly isWritable: boolean;
     readonly isSigner: boolean | 'either';
     readonly isOptional: boolean;
-    readonly docs: string[];
+    readonly docs: Docs;
 
     // Children.
     readonly defaultValue?: TDefaultValue;

+ 3 - 3
packages/node-types/src/InstructionArgumentNode.ts

@@ -1,5 +1,5 @@
 import type { InstructionInputValueNode } from './contextualValueNodes';
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 import type { TypeNode } from './typeNodes';
 
 export interface InstructionArgumentNode<
@@ -8,9 +8,9 @@ export interface InstructionArgumentNode<
     readonly kind: 'instructionArgumentNode';
 
     // Data.
-    readonly name: MainCaseString;
-    readonly docs: string[];
+    readonly name: CamelCaseString;
     readonly defaultValueStrategy?: 'omitted' | 'optional';
+    readonly docs: Docs;
 
     // Children.
     readonly type: TypeNode;

+ 3 - 3
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 { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 
 type SubInstructionNode = InstructionNode;
 
@@ -21,8 +21,8 @@ export interface InstructionNode<
     readonly kind: 'instructionNode';
 
     // Data.
-    readonly name: MainCaseString;
-    readonly docs: string[];
+    readonly name: CamelCaseString;
+    readonly docs: Docs;
     readonly optionalAccountStrategy: 'omitted' | 'programId';
 
     // Children.

+ 2 - 1
packages/node-types/src/InstructionRemainingAccountsNode.ts

@@ -1,4 +1,5 @@
 import type { ArgumentValueNode, ResolverValueNode } from './contextualValueNodes';
+import type { Docs } from './shared';
 
 export interface InstructionRemainingAccountsNode<
     TValue extends ArgumentValueNode | ResolverValueNode = ArgumentValueNode | ResolverValueNode,
@@ -6,10 +7,10 @@ export interface InstructionRemainingAccountsNode<
     readonly kind: 'instructionRemainingAccountsNode';
 
     // Data.
-    readonly docs: string[];
     readonly isOptional?: boolean;
     readonly isSigner?: boolean | 'either';
     readonly isWritable?: boolean;
+    readonly docs: Docs;
 
     // Children.
     readonly value: TValue;

+ 3 - 2
packages/node-types/src/PdaNode.ts

@@ -1,11 +1,12 @@
 import type { PdaSeedNode } from './pdaSeedNodes';
-import type { MainCaseString } from './shared';
+import type { CamelCaseString, Docs } from './shared';
 
 export interface PdaNode<TSeeds extends PdaSeedNode[] = PdaSeedNode[]> {
     readonly kind: 'pdaNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
+    readonly docs: Docs;
 
     // Children.
     readonly seeds: TSeeds;

+ 3 - 3
packages/node-types/src/ProgramNode.ts

@@ -3,7 +3,7 @@ import type { DefinedTypeNode } from './DefinedTypeNode';
 import type { ErrorNode } from './ErrorNode';
 import type { InstructionNode } from './InstructionNode';
 import type { PdaNode } from './PdaNode';
-import type { MainCaseString, ProgramVersion } from './shared';
+import type { CamelCaseString, Docs, ProgramVersion } from './shared';
 
 export interface ProgramNode<
     TPdas extends PdaNode[] = PdaNode[],
@@ -15,11 +15,11 @@ export interface ProgramNode<
     readonly kind: 'programNode';
 
     // Data.
-    readonly name: MainCaseString;
-    readonly prefix: MainCaseString;
+    readonly name: CamelCaseString;
     readonly publicKey: string;
     readonly version: ProgramVersion;
     readonly origin?: 'anchor' | 'shank';
+    readonly docs: Docs;
 
     // Children.
     readonly accounts: TAccounts;

+ 2 - 2
packages/node-types/src/contextualValueNodes/AccountBumpValueNode.ts

@@ -1,8 +1,8 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface AccountBumpValueNode {
     readonly kind: 'accountBumpValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
 }

+ 2 - 2
packages/node-types/src/contextualValueNodes/AccountValueNode.ts

@@ -1,8 +1,8 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface AccountValueNode {
     readonly kind: 'accountValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
 }

+ 2 - 2
packages/node-types/src/contextualValueNodes/ArgumentValueNode.ts

@@ -1,8 +1,8 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface ArgumentValueNode {
     readonly kind: 'argumentValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
 }

+ 2 - 3
packages/node-types/src/contextualValueNodes/ConditionalValueNode.ts

@@ -5,13 +5,12 @@ import type { InstructionInputValueNode } from './ContextualValueNode';
 import type { ResolverValueNode } from './ResolverValueNode';
 
 type ConditionNode = AccountValueNode | ArgumentValueNode | ResolverValueNode;
-type ConditionStatementNode = InstructionInputValueNode;
 
 export interface ConditionalValueNode<
     TCondition extends ConditionNode = ConditionNode,
     TValue extends ValueNode | undefined = ValueNode | undefined,
-    TIfTrue extends ConditionStatementNode | undefined = ConditionStatementNode | undefined,
-    TIfFalse extends ConditionStatementNode | undefined = ConditionStatementNode | undefined,
+    TIfTrue extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
+    TIfFalse extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
 > {
     readonly kind: 'conditionalValueNode';
 

+ 2 - 2
packages/node-types/src/contextualValueNodes/PdaSeedValueNode.ts

@@ -1,4 +1,4 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 import type { ValueNode } from '../valueNodes';
 import type { AccountValueNode } from './AccountValueNode';
 import type { ArgumentValueNode } from './ArgumentValueNode';
@@ -9,7 +9,7 @@ export interface PdaSeedValueNode<
     readonly kind: 'pdaSeedValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
 
     // Children.
     readonly value: TValue;

+ 3 - 3
packages/node-types/src/contextualValueNodes/ResolverValueNode.ts

@@ -1,4 +1,4 @@
-import type { ImportFrom, MainCaseString } from '../shared';
+import type { CamelCaseString, Docs, ImportFrom } from '../shared';
 import type { AccountValueNode } from './AccountValueNode';
 import type { ArgumentValueNode } from './ArgumentValueNode';
 
@@ -8,9 +8,9 @@ export interface ResolverValueNode<
     readonly kind: 'resolverValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly importFrom?: ImportFrom;
-    readonly docs: string[];
+    readonly docs: Docs;
 
     // Children.
     readonly dependsOn?: TDependsOn;

+ 2 - 2
packages/node-types/src/discriminatorNodes/FieldDiscriminatorNode.ts

@@ -1,9 +1,9 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface FieldDiscriminatorNode {
     readonly kind: 'fieldDiscriminatorNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly offset: number;
 }

+ 2 - 2
packages/node-types/src/linkNodes/AccountLinkNode.ts

@@ -1,9 +1,9 @@
-import type { ImportFrom, MainCaseString } from '../shared';
+import type { ImportFrom, CamelCaseString } from '../shared';
 
 export interface AccountLinkNode {
     readonly kind: 'accountLinkNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly importFrom?: ImportFrom;
 }

+ 2 - 2
packages/node-types/src/linkNodes/DefinedTypeLinkNode.ts

@@ -1,9 +1,9 @@
-import type { ImportFrom, MainCaseString } from '../shared';
+import type { ImportFrom, CamelCaseString } from '../shared';
 
 export interface DefinedTypeLinkNode {
     readonly kind: 'definedTypeLinkNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly importFrom?: ImportFrom;
 }

+ 2 - 2
packages/node-types/src/linkNodes/PdaLinkNode.ts

@@ -1,9 +1,9 @@
-import type { ImportFrom, MainCaseString } from '../shared';
+import type { ImportFrom, CamelCaseString } from '../shared';
 
 export interface PdaLinkNode {
     readonly kind: 'pdaLinkNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly importFrom?: ImportFrom;
 }

+ 2 - 2
packages/node-types/src/linkNodes/ProgramLinkNode.ts

@@ -1,9 +1,9 @@
-import type { ImportFrom, MainCaseString } from '../shared';
+import type { ImportFrom, CamelCaseString } from '../shared';
 
 export interface ProgramLinkNode {
     readonly kind: 'programLinkNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly importFrom?: ImportFrom;
 }

+ 3 - 3
packages/node-types/src/pdaSeedNodes/VariablePdaSeedNode.ts

@@ -1,12 +1,12 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString, Docs } from '../shared';
 import type { TypeNode } from '../typeNodes';
 
 export interface VariablePdaSeedNode<TType extends TypeNode = TypeNode> {
     readonly kind: 'variablePdaSeedNode';
 
     // Data.
-    readonly name: MainCaseString;
-    readonly docs: string[];
+    readonly name: CamelCaseString;
+    readonly docs: Docs;
 
     // Children.
     readonly type: TType;

+ 1 - 0
packages/node-types/src/shared/docs.ts

@@ -0,0 +1 @@
+export type Docs = string[];

+ 2 - 1
packages/node-types/src/shared/index.ts

@@ -1,4 +1,5 @@
 export * from './bytesEncoding';
+export * from './docs';
 export * from './importFrom';
-export * from './mainCase';
+export * from './stringCases';
 export * from './version';

+ 0 - 3
packages/node-types/src/shared/mainCase.ts

@@ -1,3 +0,0 @@
-export type MainCaseString = string & {
-    readonly __mainCaseString: unique symbol;
-};

+ 19 - 0
packages/node-types/src/shared/stringCases.ts

@@ -0,0 +1,19 @@
+export type TitleCaseString = string & {
+    readonly __stringCase: unique symbol;
+};
+
+export type PascalCaseString = string & {
+    readonly __stringCase: unique symbol;
+};
+
+export type CamelCaseString = string & {
+    readonly __stringCase: unique symbol;
+};
+
+export type KebabCaseString = string & {
+    readonly __stringCase: unique symbol;
+};
+
+export type SnakeCaseString = string & {
+    readonly __stringCase: unique symbol;
+};

+ 2 - 2
packages/node-types/src/typeNodes/EnumEmptyVariantTypeNode.ts

@@ -1,9 +1,9 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface EnumEmptyVariantTypeNode {
     readonly kind: 'enumEmptyVariantTypeNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly discriminator?: number;
 }

+ 2 - 2
packages/node-types/src/typeNodes/EnumStructVariantTypeNode.ts

@@ -1,4 +1,4 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 import type { NestedTypeNode } from './NestedTypeNode';
 import type { StructTypeNode } from './StructTypeNode';
 
@@ -8,7 +8,7 @@ export interface EnumStructVariantTypeNode<
     readonly kind: 'enumStructVariantTypeNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly discriminator?: number;
 
     // Children.

+ 2 - 2
packages/node-types/src/typeNodes/EnumTupleVariantTypeNode.ts

@@ -1,4 +1,4 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 import type { NestedTypeNode } from './NestedTypeNode';
 import type { TupleTypeNode } from './TupleTypeNode';
 
@@ -8,7 +8,7 @@ export interface EnumTupleVariantTypeNode<
     readonly kind: 'enumTupleVariantTypeNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly discriminator?: number;
 
     // Children.

+ 3 - 3
packages/node-types/src/typeNodes/StructFieldTypeNode.ts

@@ -1,4 +1,4 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString, Docs } from '../shared';
 import type { ValueNode } from '../valueNodes';
 import type { TypeNode } from './TypeNode';
 
@@ -9,9 +9,9 @@ export interface StructFieldTypeNode<
     readonly kind: 'structFieldTypeNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
     readonly defaultValueStrategy?: 'omitted' | 'optional';
-    readonly docs: string[];
+    readonly docs: Docs;
 
     // Children.
     readonly type: TType;

+ 2 - 2
packages/node-types/src/valueNodes/EnumValueNode.ts

@@ -1,5 +1,5 @@
 import type { DefinedTypeLinkNode } from '../linkNodes';
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 import type { StructValueNode } from './StructValueNode';
 import type { TupleValueNode } from './TupleValueNode';
 
@@ -10,7 +10,7 @@ export interface EnumValueNode<
     readonly kind: 'enumValueNode';
 
     // Data.
-    readonly variant: MainCaseString;
+    readonly variant: CamelCaseString;
 
     // Children.
     readonly enum: TEnum;

+ 2 - 2
packages/node-types/src/valueNodes/PublicKeyValueNode.ts

@@ -1,9 +1,9 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 
 export interface PublicKeyValueNode {
     readonly kind: 'publicKeyValueNode';
 
     // Data.
     readonly publicKey: string;
-    readonly identifier?: MainCaseString;
+    readonly identifier?: CamelCaseString;
 }

+ 2 - 2
packages/node-types/src/valueNodes/StructFieldValueNode.ts

@@ -1,11 +1,11 @@
-import type { MainCaseString } from '../shared';
+import type { CamelCaseString } from '../shared';
 import type { ValueNode } from './ValueNode';
 
 export interface StructFieldValueNode<TValue extends ValueNode = ValueNode> {
     readonly kind: 'structFieldValueNode';
 
     // Data.
-    readonly name: MainCaseString;
+    readonly name: CamelCaseString;
 
     // Children.
     readonly value: TValue;

+ 6 - 0
packages/nodes/.eslintrc.cjs

@@ -0,0 +1,6 @@
+module.exports = {
+    extends: ['../../.eslintrc.js'],
+    rules: {
+        'sort-keys-fix/sort-keys-fix': 'off',
+    },
+};

+ 45 - 0
packages/nodes/src/AccountNode.ts

@@ -0,0 +1,45 @@
+import type {
+    AccountNode,
+    DiscriminatorNode,
+    NestedTypeNode,
+    PdaLinkNode,
+    StructTypeNode,
+} from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+import { structTypeNode } from './typeNodes';
+
+export type AccountNodeInput<
+    TData extends NestedTypeNode<StructTypeNode> = NestedTypeNode<StructTypeNode>,
+    TPda extends PdaLinkNode | undefined = PdaLinkNode | undefined,
+    TDiscriminators extends DiscriminatorNode[] | undefined = DiscriminatorNode[] | undefined,
+> = Omit<AccountNode<TData, TPda, TDiscriminators>, 'data' | 'docs' | 'kind' | 'name'> & {
+    readonly data?: TData;
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function accountNode<
+    TData extends NestedTypeNode<StructTypeNode> = StructTypeNode<[]>,
+    TPda extends PdaLinkNode | undefined = undefined,
+    const TDiscriminators extends DiscriminatorNode[] | undefined = undefined,
+>(input: AccountNodeInput<TData, TPda, TDiscriminators>): AccountNode<TData, TPda, TDiscriminators> {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('AccountNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'accountNode',
+
+        // Data.
+        name: camelCase(input.name),
+        ...(input.size === undefined ? {} : { size: input.size }),
+        docs: parseDocs(input.docs),
+
+        // Children.
+        data: (input.data ?? structTypeNode([])) as TData,
+        ...(input.pda !== undefined && { pda: input.pda }),
+        ...(input.discriminators !== undefined && { discriminators: input.discriminators }),
+    });
+}

+ 29 - 0
packages/nodes/src/DefinedTypeNode.ts

@@ -0,0 +1,29 @@
+import type { DefinedTypeNode, TypeNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+export type DefinedTypeNodeInput<TType extends TypeNode = TypeNode> = Omit<
+    DefinedTypeNode<TType>,
+    'docs' | 'kind' | 'name'
+> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function definedTypeNode<TType extends TypeNode>(input: DefinedTypeNodeInput<TType>): DefinedTypeNode<TType> {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('DefinedTypeNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'definedTypeNode',
+
+        // Data.
+        name: camelCase(input.name),
+        docs: parseDocs(input.docs),
+
+        // Children.
+        type: input.type,
+    });
+}

+ 30 - 0
packages/nodes/src/ErrorNode.ts

@@ -0,0 +1,30 @@
+import type { ErrorNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+export type ErrorNodeInput = Omit<ErrorNode, 'docs' | 'kind' | 'name'> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function errorNode(input: ErrorNodeInput): ErrorNode {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('ErrorNode must have a name.');
+    }
+
+    if (input.code < 0) {
+        // TODO: Coded error.
+        throw new Error('ErrorNode must have a code number.');
+    }
+
+    return Object.freeze({
+        kind: 'errorNode',
+
+        // Data.
+        name: camelCase(input.name),
+        code: input.code,
+        message: input.message,
+        docs: parseDocs(input.docs),
+    });
+}

+ 29 - 0
packages/nodes/src/InstructionAccountNode.ts

@@ -0,0 +1,29 @@
+import type { InstructionAccountNode, InstructionInputValueNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+export type InstructionAccountNodeInput<
+    TDefaultValue extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
+> = Omit<InstructionAccountNode<TDefaultValue>, 'docs' | 'isOptional' | 'kind' | 'name'> & {
+    readonly docs?: DocsInput;
+    readonly isOptional?: boolean;
+    readonly name: string;
+};
+
+export function instructionAccountNode<TDefaultValue extends InstructionInputValueNode | undefined = undefined>(
+    input: InstructionAccountNodeInput<TDefaultValue>,
+): InstructionAccountNode<TDefaultValue> {
+    return Object.freeze({
+        kind: 'instructionAccountNode',
+
+        // Data.
+        name: camelCase(input.name),
+        isWritable: input.isWritable,
+        isSigner: input.isSigner,
+        isOptional: input.isOptional ?? false,
+        docs: parseDocs(input.docs),
+
+        // Children.
+        ...(input.defaultValue !== undefined && { defaultValue: input.defaultValue }),
+    });
+}

+ 51 - 0
packages/nodes/src/InstructionArgumentNode.ts

@@ -0,0 +1,51 @@
+import type { InstructionArgumentNode, InstructionInputValueNode } from '@kinobi-so/node-types';
+
+import { isNode } from './Node';
+import { camelCase, DocsInput, parseDocs } from './shared';
+import { structFieldTypeNode } from './typeNodes/StructFieldTypeNode';
+import { structTypeNode } from './typeNodes/StructTypeNode';
+import { VALUE_NODES } from './valueNodes';
+
+export type InstructionArgumentNodeInput<
+    TDefaultValue extends InstructionInputValueNode | undefined = InstructionInputValueNode | undefined,
+> = Omit<InstructionArgumentNode<TDefaultValue>, 'docs' | 'kind' | 'name'> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function instructionArgumentNode<TDefaultValue extends InstructionInputValueNode | undefined = undefined>(
+    input: InstructionArgumentNodeInput<TDefaultValue>,
+): InstructionArgumentNode<TDefaultValue> {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('InstructionArgumentNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'instructionArgumentNode',
+
+        // Data.
+        name: camelCase(input.name),
+        ...(input.defaultValueStrategy !== undefined && { defaultValueStrategy: input.defaultValueStrategy }),
+        docs: parseDocs(input.docs),
+
+        // Children.
+        type: input.type,
+        ...(input.defaultValue !== undefined && { defaultValue: input.defaultValue }),
+    });
+}
+
+export function structTypeNodeFromInstructionArgumentNodes(nodes: InstructionArgumentNode[]) {
+    return structTypeNode(nodes.map(structFieldTypeNodeFromInstructionArgumentNode));
+}
+
+export function structFieldTypeNodeFromInstructionArgumentNode(node: InstructionArgumentNode) {
+    if (isNode(node.defaultValue, VALUE_NODES)) {
+        return structFieldTypeNode({ ...node, defaultValue: node.defaultValue });
+    }
+    return structFieldTypeNode({
+        ...node,
+        defaultValue: undefined,
+        defaultValueStrategy: undefined,
+    });
+}

+ 22 - 0
packages/nodes/src/InstructionByteDeltaNode.ts

@@ -0,0 +1,22 @@
+import type { InstructionByteDeltaNode } from '@kinobi-so/node-types';
+
+import { isNode } from './Node';
+
+export function instructionByteDeltaNode<TValue extends InstructionByteDeltaNode['value']>(
+    value: TValue,
+    options: {
+        subtract?: boolean;
+        withHeader?: boolean;
+    } = {},
+): InstructionByteDeltaNode<TValue> {
+    return Object.freeze({
+        kind: 'instructionByteDeltaNode',
+
+        // Data.
+        withHeader: options.withHeader ?? !isNode(value, 'resolverValueNode'),
+        ...(options.subtract !== undefined && { subtract: options.subtract }),
+
+        // Children.
+        value,
+    });
+}

+ 116 - 0
packages/nodes/src/InstructionNode.ts

@@ -0,0 +1,116 @@
+import type {
+    DiscriminatorNode,
+    InstructionAccountNode,
+    InstructionArgumentNode,
+    InstructionByteDeltaNode,
+    InstructionNode,
+    InstructionRemainingAccountsNode,
+    ProgramNode,
+    RootNode,
+} from '@kinobi-so/node-types';
+
+import { isNode } from './Node';
+import { getAllInstructions } from './ProgramNode';
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+type SubInstructionNode = InstructionNode;
+
+export type InstructionNodeInput<
+    TAccounts extends InstructionAccountNode[] = InstructionAccountNode[],
+    TArguments extends InstructionArgumentNode[] = InstructionArgumentNode[],
+    TExtraArguments extends InstructionArgumentNode[] | undefined = InstructionArgumentNode[] | undefined,
+    TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined =
+        | InstructionRemainingAccountsNode[]
+        | undefined,
+    TByteDeltas extends InstructionByteDeltaNode[] | undefined = InstructionByteDeltaNode[] | undefined,
+    TDiscriminators extends DiscriminatorNode[] | undefined = DiscriminatorNode[] | undefined,
+    TSubInstructions extends SubInstructionNode[] | undefined = SubInstructionNode[] | undefined,
+> = Omit<
+    Partial<
+        InstructionNode<
+            TAccounts,
+            TArguments,
+            TExtraArguments,
+            TRemainingAccounts,
+            TByteDeltas,
+            TDiscriminators,
+            TSubInstructions
+        >
+    >,
+    'docs' | 'kind' | 'name'
+> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function instructionNode<
+    const TAccounts extends InstructionAccountNode[] = [],
+    const TArguments extends InstructionArgumentNode[] = [],
+    const TExtraArguments extends InstructionArgumentNode[] | undefined = undefined,
+    const TRemainingAccounts extends InstructionRemainingAccountsNode[] | undefined = undefined,
+    const TByteDeltas extends InstructionByteDeltaNode[] | undefined = undefined,
+    const TDiscriminators extends DiscriminatorNode[] | undefined = undefined,
+    const TSubInstructions extends SubInstructionNode[] | undefined = undefined,
+>(
+    input: InstructionNodeInput<
+        TAccounts,
+        TArguments,
+        TExtraArguments,
+        TRemainingAccounts,
+        TByteDeltas,
+        TDiscriminators,
+        TSubInstructions
+    >,
+): InstructionNode<
+    TAccounts,
+    TArguments,
+    TExtraArguments,
+    TRemainingAccounts,
+    TByteDeltas,
+    TDiscriminators,
+    TSubInstructions
+> {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('InstructionNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'instructionNode',
+
+        // Data.
+        name: camelCase(input.name),
+        docs: parseDocs(input.docs),
+        optionalAccountStrategy: input.optionalAccountStrategy ?? 'programId',
+
+        // Children.
+        accounts: (input.accounts ?? []) as TAccounts,
+        arguments: (input.arguments ?? []) as TArguments,
+        extraArguments: input.extraArguments,
+        remainingAccounts: input.remainingAccounts,
+        byteDeltas: input.byteDeltas,
+        discriminators: input.discriminators,
+        subInstructions: input.subInstructions,
+    });
+}
+
+export function getAllInstructionArguments(node: InstructionNode): InstructionArgumentNode[] {
+    return [...node.arguments, ...(node.extraArguments ?? [])];
+}
+
+export function getAllInstructionsWithSubs(
+    node: InstructionNode | ProgramNode | RootNode,
+    config: { leavesOnly?: boolean; subInstructionsFirst?: boolean } = {},
+): InstructionNode[] {
+    const { leavesOnly = false, subInstructionsFirst = false } = config;
+    if (isNode(node, 'instructionNode')) {
+        if (!node.subInstructions || node.subInstructions.length === 0) return [node];
+        const subInstructions = node.subInstructions.flatMap(sub => getAllInstructionsWithSubs(sub, config));
+        if (leavesOnly) return subInstructions;
+        return subInstructionsFirst ? [...subInstructions, node] : [node, ...subInstructions];
+    }
+
+    const instructions = isNode(node, 'programNode') ? node.instructions : getAllInstructions(node);
+
+    return instructions.flatMap(instruction => getAllInstructionsWithSubs(instruction, config));
+}

+ 26 - 0
packages/nodes/src/InstructionRemainingAccountsNode.ts

@@ -0,0 +1,26 @@
+import type { ArgumentValueNode, InstructionRemainingAccountsNode, ResolverValueNode } from '@kinobi-so/node-types';
+
+import { DocsInput, parseDocs } from './shared';
+
+export function instructionRemainingAccountsNode<TValue extends ArgumentValueNode | ResolverValueNode>(
+    value: TValue,
+    options: {
+        docs?: DocsInput;
+        isOptional?: boolean;
+        isSigner?: boolean | 'either';
+        isWritable?: boolean;
+    } = {},
+): InstructionRemainingAccountsNode<TValue> {
+    return Object.freeze({
+        kind: 'instructionRemainingAccountsNode',
+
+        // Data.
+        ...(options.isOptional !== undefined && { isOptional: options.isOptional }),
+        ...(options.isSigner !== undefined && { isSigner: options.isSigner }),
+        ...(options.isWritable !== undefined && { isWritable: options.isWritable }),
+        docs: parseDocs(options.docs),
+
+        // Children.
+        value,
+    });
+}

+ 75 - 0
packages/nodes/src/Node.ts

@@ -0,0 +1,75 @@
+import type { GetNodeFromKind, Node, NodeKind } from '@kinobi-so/node-types';
+
+import { REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS } from './contextualValueNodes/ContextualValueNode';
+import { REGISTERED_COUNT_NODE_KINDS } from './countNodes/CountNode';
+import { REGISTERED_DISCRIMINATOR_NODE_KINDS } from './discriminatorNodes/DiscriminatorNode';
+import { REGISTERED_LINK_NODE_KINDS } from './linkNodes/LinkNode';
+import { REGISTERED_PDA_SEED_NODE_KINDS } from './pdaSeedNodes/PdaSeedNode';
+import { REGISTERED_TYPE_NODE_KINDS } from './typeNodes/TypeNode';
+import { REGISTERED_VALUE_NODE_KINDS } from './valueNodes/ValueNode';
+
+// Node Registration.
+export const REGISTERED_NODE_KINDS = [
+    ...REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS,
+    ...REGISTERED_DISCRIMINATOR_NODE_KINDS,
+    ...REGISTERED_LINK_NODE_KINDS,
+    ...REGISTERED_PDA_SEED_NODE_KINDS,
+    ...REGISTERED_COUNT_NODE_KINDS,
+    ...REGISTERED_TYPE_NODE_KINDS,
+    ...REGISTERED_VALUE_NODE_KINDS,
+    'rootNode' as const,
+    'programNode' as const,
+    'pdaNode' as const,
+    'accountNode' as const,
+    'instructionAccountNode' as const,
+    'instructionArgumentNode' as const,
+    'instructionByteDeltaNode' as const,
+    'instructionNode' as const,
+    'instructionRemainingAccountsNode' as const,
+    'errorNode' as const,
+    'definedTypeNode' as const,
+];
+
+// Node Helpers.
+
+export function isNode<TKind extends NodeKind>(
+    node: Node | null | undefined,
+    kind: TKind | TKind[],
+): node is GetNodeFromKind<TKind> {
+    const kinds = Array.isArray(kind) ? kind : [kind];
+    return !!node && (kinds as NodeKind[]).includes(node.kind);
+}
+
+export function assertIsNode<TKind extends NodeKind>(
+    node: Node | null | undefined,
+    kind: TKind | TKind[],
+): asserts node is GetNodeFromKind<TKind> {
+    const kinds = Array.isArray(kind) ? kind : [kind];
+    if (!isNode(node, kinds)) {
+        throw new Error(`Expected ${kinds.join(' | ')}, got ${node?.kind ?? 'null'}.`);
+    }
+}
+
+export function isNodeFilter<TKind extends NodeKind>(
+    kind: TKind | TKind[],
+): (node: Node | null | undefined) => node is GetNodeFromKind<TKind> {
+    return (node): node is GetNodeFromKind<TKind> => isNode(node, kind);
+}
+
+export function assertIsNodeFilter<TKind extends NodeKind>(
+    kind: TKind | TKind[],
+): (node: Node | null | undefined) => node is GetNodeFromKind<TKind> {
+    return (node): node is GetNodeFromKind<TKind> => {
+        assertIsNode(node, kind);
+        return true;
+    };
+}
+
+export function removeNullAndAssertIsNodeFilter<TKind extends NodeKind>(
+    kind: TKind | TKind[],
+): (node: Node | null | undefined) => node is GetNodeFromKind<TKind> {
+    return (node): node is GetNodeFromKind<TKind> => {
+        if (node) assertIsNode(node, kind);
+        return node != null;
+    };
+}

+ 29 - 0
packages/nodes/src/PdaNode.ts

@@ -0,0 +1,29 @@
+import type { PdaNode, PdaSeedNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+export type PdaNodeInput<TSeeds extends PdaSeedNode[] = PdaSeedNode[]> = Omit<
+    PdaNode<TSeeds>,
+    'docs' | 'kind' | 'name'
+> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+};
+
+export function pdaNode<const TSeeds extends PdaSeedNode[]>(input: PdaNodeInput<TSeeds>): PdaNode<TSeeds> {
+    if (!input.name) {
+        // TODO: Coded error.
+        throw new Error('PdaNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'pdaNode',
+
+        // Data.
+        name: camelCase(input.name),
+        docs: parseDocs(input.docs),
+
+        // Children.
+        seeds: input.seeds,
+    });
+}

+ 80 - 0
packages/nodes/src/ProgramNode.ts

@@ -0,0 +1,80 @@
+import type {
+    AccountNode,
+    DefinedTypeNode,
+    ErrorNode,
+    InstructionNode,
+    PdaNode,
+    ProgramNode,
+    RootNode,
+} from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from './shared';
+
+export type ProgramNodeInput<
+    TPdas extends PdaNode[] = PdaNode[],
+    TAccounts extends AccountNode[] = AccountNode[],
+    TInstructions extends InstructionNode[] = InstructionNode[],
+    TDefinedTypes extends DefinedTypeNode[] = DefinedTypeNode[],
+    TErrors extends ErrorNode[] = ErrorNode[],
+> = Omit<
+    Partial<ProgramNode<TPdas, TAccounts, TInstructions, TDefinedTypes, TErrors>>,
+    'docs' | 'kind' | 'name' | 'publicKey'
+> & {
+    readonly docs?: DocsInput;
+    readonly name: string;
+    readonly publicKey: ProgramNode['publicKey'];
+};
+
+export function programNode<
+    const TPdas extends PdaNode[] = [],
+    const TAccounts extends AccountNode[] = [],
+    const TInstructions extends InstructionNode[] = [],
+    const TDefinedTypes extends DefinedTypeNode[] = [],
+    const TErrors extends ErrorNode[] = [],
+>(
+    input: ProgramNodeInput<TPdas, TAccounts, TInstructions, TDefinedTypes, TErrors>,
+): ProgramNode<TPdas, TAccounts, TInstructions, TDefinedTypes, TErrors> {
+    return Object.freeze({
+        kind: 'programNode',
+
+        // Data.
+        name: camelCase(input.name),
+        publicKey: input.publicKey,
+        version: input.version ?? '0.0.0',
+        ...(input.origin !== undefined && { origin: input.origin }),
+        docs: parseDocs(input.docs),
+
+        // Children.
+        accounts: (input.accounts ?? []) as TAccounts,
+        instructions: (input.instructions ?? []) as TInstructions,
+        definedTypes: (input.definedTypes ?? []) as TDefinedTypes,
+        pdas: (input.pdas ?? []) as TPdas,
+        errors: (input.errors ?? []) as TErrors,
+    });
+}
+
+export function getAllPrograms(node: ProgramNode | ProgramNode[] | RootNode): ProgramNode[] {
+    if (Array.isArray(node)) return node;
+    if (node.kind === 'programNode') return [node];
+    return [node.program, ...node.additionalPrograms];
+}
+
+export function getAllPdas(node: ProgramNode | ProgramNode[] | RootNode): PdaNode[] {
+    return getAllPrograms(node).flatMap(program => program.pdas);
+}
+
+export function getAllAccounts(node: ProgramNode | ProgramNode[] | RootNode): AccountNode[] {
+    return getAllPrograms(node).flatMap(program => program.accounts);
+}
+
+export function getAllDefinedTypes(node: ProgramNode | ProgramNode[] | RootNode): DefinedTypeNode[] {
+    return getAllPrograms(node).flatMap(program => program.definedTypes);
+}
+
+export function getAllInstructions(node: ProgramNode | ProgramNode[] | RootNode): InstructionNode[] {
+    return getAllPrograms(node).flatMap(program => program.instructions);
+}
+
+export function getAllErrors(node: ProgramNode | ProgramNode[] | RootNode): ErrorNode[] {
+    return getAllPrograms(node).flatMap(program => program.errors);
+}

+ 18 - 0
packages/nodes/src/RootNode.ts

@@ -0,0 +1,18 @@
+import type { KinobiVersion, ProgramNode, RootNode } from '@kinobi-so/node-types';
+
+export function rootNode<TProgram extends ProgramNode, const TAdditionalPrograms extends ProgramNode[] = []>(
+    program: TProgram,
+    additionalPrograms?: TAdditionalPrograms,
+): RootNode<TProgram, TAdditionalPrograms> {
+    return Object.freeze({
+        kind: 'rootNode',
+
+        // Data.
+        standard: 'kinobi',
+        version: __VERSION__ as KinobiVersion,
+
+        // Children.
+        program,
+        additionalPrograms: (additionalPrograms ?? []) as TAdditionalPrograms,
+    });
+}

+ 12 - 0
packages/nodes/src/contextualValueNodes/AccountBumpValueNode.ts

@@ -0,0 +1,12 @@
+import type { AccountBumpValueNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function accountBumpValueNode(name: string): AccountBumpValueNode {
+    return Object.freeze({
+        kind: 'accountBumpValueNode',
+
+        // Data.
+        name: camelCase(name),
+    });
+}

+ 12 - 0
packages/nodes/src/contextualValueNodes/AccountValueNode.ts

@@ -0,0 +1,12 @@
+import type { AccountValueNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function accountValueNode(name: string): AccountValueNode {
+    return Object.freeze({
+        kind: 'accountValueNode',
+
+        // Data.
+        name: camelCase(name),
+    });
+}

+ 12 - 0
packages/nodes/src/contextualValueNodes/ArgumentValueNode.ts

@@ -0,0 +1,12 @@
+import type { ArgumentValueNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function argumentValueNode(name: string): ArgumentValueNode {
+    return Object.freeze({
+        kind: 'argumentValueNode',
+
+        // Data.
+        name: camelCase(name),
+    });
+}

+ 32 - 0
packages/nodes/src/contextualValueNodes/ConditionalValueNode.ts

@@ -0,0 +1,32 @@
+import type {
+    AccountValueNode,
+    ArgumentValueNode,
+    ConditionalValueNode,
+    InstructionInputValueNode,
+    ResolverValueNode,
+    ValueNode,
+} from '@kinobi-so/node-types';
+
+type ConditionNode = AccountValueNode | ArgumentValueNode | ResolverValueNode;
+
+export function conditionalValueNode<
+    TCondition extends ConditionNode,
+    TValue extends ValueNode | undefined = undefined,
+    TIfTrue extends InstructionInputValueNode | undefined = undefined,
+    TIfFalse extends InstructionInputValueNode | undefined = undefined,
+>(input: {
+    condition: TCondition;
+    ifFalse?: TIfFalse;
+    ifTrue?: TIfTrue;
+    value?: TValue;
+}): ConditionalValueNode<TCondition, TValue, TIfTrue, TIfFalse> {
+    return Object.freeze({
+        kind: 'conditionalValueNode',
+
+        // Children.
+        condition: input.condition,
+        ...(input.value !== undefined && { value: input.value }),
+        ...(input.ifTrue !== undefined && { ifTrue: input.ifTrue }),
+        ...(input.ifFalse !== undefined && { ifFalse: input.ifFalse }),
+    });
+}

+ 24 - 0
packages/nodes/src/contextualValueNodes/ContextualValueNode.ts

@@ -0,0 +1,24 @@
+import { VALUE_NODES } from '../valueNodes/ValueNode';
+
+// Standalone Contextual Value Node Registration.
+export const STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS = [
+    'accountBumpValueNode' as const,
+    'accountValueNode' as const,
+    'argumentValueNode' as const,
+    'conditionalValueNode' as const,
+    'identityValueNode' as const,
+    'payerValueNode' as const,
+    'pdaValueNode' as const,
+    'programIdValueNode' as const,
+    'resolverValueNode' as const,
+];
+
+// Contextual Value Node Registration.
+export const REGISTERED_CONTEXTUAL_VALUE_NODE_KINDS = [
+    ...STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS,
+    'pdaSeedValueNode' as const,
+];
+
+// Contextual Value Node Helpers.
+export const CONTEXTUAL_VALUE_NODES = STANDALONE_CONTEXTUAL_VALUE_NODE_KINDS;
+export const INSTRUCTION_INPUT_VALUE_NODE = [...VALUE_NODES, ...CONTEXTUAL_VALUE_NODES, 'programLinkNode' as const];

+ 5 - 0
packages/nodes/src/contextualValueNodes/IdentityValueNode.ts

@@ -0,0 +1,5 @@
+import type { IdentityValueNode } from '@kinobi-so/node-types';
+
+export function identityValueNode(): IdentityValueNode {
+    return Object.freeze({ kind: 'identityValueNode' });
+}

+ 5 - 0
packages/nodes/src/contextualValueNodes/PayerValueNode.ts

@@ -0,0 +1,5 @@
+import type { PayerValueNode } from '@kinobi-so/node-types';
+
+export function payerValueNode(): PayerValueNode {
+    return Object.freeze({ kind: 'payerValueNode' });
+}

+ 17 - 0
packages/nodes/src/contextualValueNodes/PdaSeedValueNode.ts

@@ -0,0 +1,17 @@
+import type { AccountValueNode, ArgumentValueNode, PdaSeedValueNode, ValueNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function pdaSeedValueNode<
+    TValue extends AccountValueNode | ArgumentValueNode | ValueNode = AccountValueNode | ArgumentValueNode | ValueNode,
+>(name: string, value: TValue): PdaSeedValueNode<TValue> {
+    return Object.freeze({
+        kind: 'pdaSeedValueNode',
+
+        // Data.
+        name: camelCase(name),
+
+        // Children.
+        value,
+    });
+}

+ 16 - 0
packages/nodes/src/contextualValueNodes/PdaValueNode.ts

@@ -0,0 +1,16 @@
+import type { PdaLinkNode, PdaSeedValueNode, PdaValueNode } from '@kinobi-so/node-types';
+
+import { pdaLinkNode } from '../linkNodes';
+
+export function pdaValueNode<const TSeeds extends PdaSeedValueNode[] = []>(
+    pda: PdaLinkNode | string,
+    seeds: TSeeds = [] as PdaSeedValueNode[] as TSeeds,
+): PdaValueNode<TSeeds> {
+    return Object.freeze({
+        kind: 'pdaValueNode',
+
+        // Children.
+        pda: typeof pda === 'string' ? pdaLinkNode(pda) : pda,
+        seeds,
+    });
+}

+ 5 - 0
packages/nodes/src/contextualValueNodes/ProgramIdValueNode.ts

@@ -0,0 +1,5 @@
+import type { ProgramIdValueNode } from '@kinobi-so/node-types';
+
+export function programIdValueNode(): ProgramIdValueNode {
+    return Object.freeze({ kind: 'programIdValueNode' });
+}

+ 24 - 0
packages/nodes/src/contextualValueNodes/ResolverValueNode.ts

@@ -0,0 +1,24 @@
+import type { AccountValueNode, ArgumentValueNode, ImportFrom, ResolverValueNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from '../shared';
+
+export function resolverValueNode<const TDependsOn extends (AccountValueNode | ArgumentValueNode)[] = []>(
+    name: string,
+    options: {
+        dependsOn?: TDependsOn;
+        docs?: DocsInput;
+        importFrom?: ImportFrom;
+    } = {},
+): ResolverValueNode<TDependsOn> {
+    return Object.freeze({
+        kind: 'resolverValueNode',
+
+        // Data.
+        name: camelCase(name),
+        ...(options.importFrom && { importFrom: options.importFrom }),
+        docs: parseDocs(options.docs),
+
+        // Children.
+        dependsOn: options.dependsOn,
+    });
+}

+ 11 - 0
packages/nodes/src/contextualValueNodes/index.ts

@@ -0,0 +1,11 @@
+export * from './AccountBumpValueNode';
+export * from './AccountValueNode';
+export * from './ArgumentValueNode';
+export * from './ConditionalValueNode';
+export * from './ContextualValueNode';
+export * from './IdentityValueNode';
+export * from './PayerValueNode';
+export * from './PdaSeedValueNode';
+export * from './PdaValueNode';
+export * from './ProgramIdValueNode';
+export * from './ResolverValueNode';

+ 9 - 0
packages/nodes/src/countNodes/CountNode.ts

@@ -0,0 +1,9 @@
+// Count Node Registration.
+export const REGISTERED_COUNT_NODE_KINDS = [
+    'fixedCountNode' as const,
+    'remainderCountNode' as const,
+    'prefixedCountNode' as const,
+];
+
+// Count Node Helpers.
+export const COUNT_NODES = REGISTERED_COUNT_NODE_KINDS;

+ 10 - 0
packages/nodes/src/countNodes/FixedCountNode.ts

@@ -0,0 +1,10 @@
+import type { FixedCountNode } from '@kinobi-so/node-types';
+
+export function fixedCountNode(value: number): FixedCountNode {
+    return Object.freeze({
+        kind: 'fixedCountNode',
+
+        // Data.
+        value,
+    });
+}

+ 12 - 0
packages/nodes/src/countNodes/PrefixedCountNode.ts

@@ -0,0 +1,12 @@
+import type { NestedTypeNode, NumberTypeNode, PrefixedCountNode } from '@kinobi-so/node-types';
+
+export function prefixedCountNode<TPrefix extends NestedTypeNode<NumberTypeNode>>(
+    prefix: TPrefix,
+): PrefixedCountNode<TPrefix> {
+    return Object.freeze({
+        kind: 'prefixedCountNode',
+
+        // Children.
+        prefix,
+    });
+}

+ 5 - 0
packages/nodes/src/countNodes/RemainderCountNode.ts

@@ -0,0 +1,5 @@
+import type { RemainderCountNode } from '@kinobi-so/node-types';
+
+export function remainderCountNode(): RemainderCountNode {
+    return Object.freeze({ kind: 'remainderCountNode' });
+}

+ 4 - 0
packages/nodes/src/countNodes/index.ts

@@ -0,0 +1,4 @@
+export * from './CountNode';
+export * from './FixedCountNode';
+export * from './PrefixedCountNode';
+export * from './RemainderCountNode';

+ 16 - 0
packages/nodes/src/discriminatorNodes/ConstantDiscriminatorNode.ts

@@ -0,0 +1,16 @@
+import type { ConstantDiscriminatorNode, ConstantValueNode } from '@kinobi-so/node-types';
+
+export function constantDiscriminatorNode<TConstant extends ConstantValueNode>(
+    constant: TConstant,
+    offset: number = 0,
+): ConstantDiscriminatorNode {
+    return Object.freeze({
+        kind: 'constantDiscriminatorNode',
+
+        // Data.
+        offset,
+
+        // Children.
+        constant,
+    });
+}

+ 9 - 0
packages/nodes/src/discriminatorNodes/DiscriminatorNode.ts

@@ -0,0 +1,9 @@
+// Discriminator Node Registration.
+export const REGISTERED_DISCRIMINATOR_NODE_KINDS = [
+    'constantDiscriminatorNode' as const,
+    'fieldDiscriminatorNode' as const,
+    'sizeDiscriminatorNode' as const,
+];
+
+// Discriminator Node Helpers.
+export const DISCRIMINATOR_NODES = REGISTERED_DISCRIMINATOR_NODE_KINDS;

+ 13 - 0
packages/nodes/src/discriminatorNodes/FieldDiscriminatorNode.ts

@@ -0,0 +1,13 @@
+import type { FieldDiscriminatorNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function fieldDiscriminatorNode(name: string, offset: number = 0): FieldDiscriminatorNode {
+    return Object.freeze({
+        kind: 'fieldDiscriminatorNode',
+
+        // Data.
+        name: camelCase(name),
+        offset,
+    });
+}

+ 10 - 0
packages/nodes/src/discriminatorNodes/SizeDiscriminatorNode.ts

@@ -0,0 +1,10 @@
+import type { SizeDiscriminatorNode } from '@kinobi-so/node-types';
+
+export function sizeDiscriminatorNode(size: number): SizeDiscriminatorNode {
+    return Object.freeze({
+        kind: 'sizeDiscriminatorNode',
+
+        // Data.
+        size,
+    });
+}

+ 4 - 0
packages/nodes/src/discriminatorNodes/index.ts

@@ -0,0 +1,4 @@
+export * from './ConstantDiscriminatorNode';
+export * from './DiscriminatorNode';
+export * from './FieldDiscriminatorNode';
+export * from './SizeDiscriminatorNode';

+ 22 - 2
packages/nodes/src/index.ts

@@ -1,2 +1,22 @@
-export * from './platform';
-export * from './version';
+export * from '@kinobi-so/node-types';
+
+export * from './contextualValueNodes';
+export * from './countNodes';
+export * from './discriminatorNodes';
+export * from './linkNodes';
+export * from './pdaSeedNodes';
+export * from './typeNodes';
+export * from './valueNodes';
+
+export * from './AccountNode';
+export * from './DefinedTypeNode';
+export * from './ErrorNode';
+export * from './InstructionAccountNode';
+export * from './InstructionArgumentNode';
+export * from './InstructionByteDeltaNode';
+export * from './InstructionNode';
+export * from './InstructionRemainingAccountsNode';
+export * from './Node';
+export * from './PdaNode';
+export * from './ProgramNode';
+export * from './RootNode';

+ 13 - 0
packages/nodes/src/linkNodes/AccountLinkNode.ts

@@ -0,0 +1,13 @@
+import type { AccountLinkNode, ImportFrom } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function accountLinkNode(name: string, importFrom?: ImportFrom): AccountLinkNode {
+    return Object.freeze({
+        kind: 'accountLinkNode',
+
+        // Data.
+        name: camelCase(name),
+        importFrom,
+    });
+}

+ 13 - 0
packages/nodes/src/linkNodes/DefinedTypeLinkNode.ts

@@ -0,0 +1,13 @@
+import type { DefinedTypeLinkNode, ImportFrom } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function definedTypeLinkNode(name: string, importFrom?: ImportFrom): DefinedTypeLinkNode {
+    return Object.freeze({
+        kind: 'definedTypeLinkNode',
+
+        // Data.
+        name: camelCase(name),
+        importFrom,
+    });
+}

+ 10 - 0
packages/nodes/src/linkNodes/LinkNode.ts

@@ -0,0 +1,10 @@
+// Link Node Registration.
+export const REGISTERED_LINK_NODE_KINDS = [
+    'programLinkNode' as const,
+    'pdaLinkNode' as const,
+    'accountLinkNode' as const,
+    'definedTypeLinkNode' as const,
+];
+
+// Link Node Helpers.
+export const LINK_NODES = REGISTERED_LINK_NODE_KINDS;

+ 13 - 0
packages/nodes/src/linkNodes/PdaLinkNode.ts

@@ -0,0 +1,13 @@
+import type { ImportFrom, PdaLinkNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function pdaLinkNode(name: string, importFrom?: ImportFrom): PdaLinkNode {
+    return Object.freeze({
+        kind: 'pdaLinkNode',
+
+        // Data.
+        name: camelCase(name),
+        importFrom,
+    });
+}

+ 13 - 0
packages/nodes/src/linkNodes/ProgramLinkNode.ts

@@ -0,0 +1,13 @@
+import type { ImportFrom, ProgramLinkNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function programLinkNode(name: string, importFrom?: ImportFrom): ProgramLinkNode {
+    return Object.freeze({
+        kind: 'programLinkNode',
+
+        // Data.
+        name: camelCase(name),
+        importFrom,
+    });
+}

+ 5 - 0
packages/nodes/src/linkNodes/index.ts

@@ -0,0 +1,5 @@
+export * from './AccountLinkNode';
+export * from './DefinedTypeLinkNode';
+export * from './LinkNode';
+export * from './PdaLinkNode';
+export * from './ProgramLinkNode';

+ 39 - 0
packages/nodes/src/pdaSeedNodes/ConstantPdaSeedNode.ts

@@ -0,0 +1,39 @@
+import type {
+    BytesEncoding,
+    ConstantPdaSeedNode,
+    ProgramIdValueNode,
+    TypeNode,
+    ValueNode,
+} from '@kinobi-so/node-types';
+
+import { programIdValueNode } from '../contextualValueNodes/ProgramIdValueNode';
+import { bytesTypeNode } from '../typeNodes/BytesTypeNode';
+import { publicKeyTypeNode } from '../typeNodes/PublicKeyTypeNode';
+import { stringTypeNode } from '../typeNodes/StringTypeNode';
+import { bytesValueNode } from '../valueNodes/BytesValueNode';
+import { stringValueNode } from '../valueNodes/StringValueNode';
+
+export function constantPdaSeedNode<TType extends TypeNode, TValue extends ProgramIdValueNode | ValueNode>(
+    type: TType,
+    value: TValue,
+): ConstantPdaSeedNode<TType, TValue> {
+    return Object.freeze({
+        kind: 'constantPdaSeedNode',
+
+        // Children.
+        type,
+        value,
+    });
+}
+
+export function constantPdaSeedNodeFromProgramId() {
+    return constantPdaSeedNode(publicKeyTypeNode(), programIdValueNode());
+}
+
+export function constantPdaSeedNodeFromString<TEncoding extends BytesEncoding>(encoding: TEncoding, string: string) {
+    return constantPdaSeedNode(stringTypeNode(encoding), stringValueNode(string));
+}
+
+export function constantPdaSeedNodeFromBytes<TEncoding extends BytesEncoding>(encoding: TEncoding, data: string) {
+    return constantPdaSeedNode(bytesTypeNode(), bytesValueNode(encoding, data));
+}

+ 5 - 0
packages/nodes/src/pdaSeedNodes/PdaSeedNode.ts

@@ -0,0 +1,5 @@
+// Pda Seed Node Registration.
+export const REGISTERED_PDA_SEED_NODE_KINDS = ['constantPdaSeedNode' as const, 'variablePdaSeedNode' as const];
+
+// Pda Seed Node Helpers.
+export const PDA_SEED_NODES = REGISTERED_PDA_SEED_NODE_KINDS;

+ 20 - 0
packages/nodes/src/pdaSeedNodes/VariablePdaSeedNode.ts

@@ -0,0 +1,20 @@
+import type { TypeNode, VariablePdaSeedNode } from '@kinobi-so/node-types';
+
+import { camelCase, DocsInput, parseDocs } from '../shared';
+
+export function variablePdaSeedNode<TType extends TypeNode>(
+    name: string,
+    type: TType,
+    docs?: DocsInput,
+): VariablePdaSeedNode<TType> {
+    return Object.freeze({
+        kind: 'variablePdaSeedNode',
+
+        // Data.
+        name: camelCase(name),
+        docs: parseDocs(docs),
+
+        // Children.
+        type,
+    });
+}

+ 3 - 0
packages/nodes/src/pdaSeedNodes/index.ts

@@ -0,0 +1,3 @@
+export * from './ConstantPdaSeedNode';
+export * from './PdaSeedNode';
+export * from './VariablePdaSeedNode';

+ 0 - 7
packages/nodes/src/platform.ts

@@ -1,7 +0,0 @@
-type Platform = 'browser' | 'node' | 'react-native';
-
-export function getPlatform(): Platform {
-    if (__BROWSER__) return 'browser';
-    if (__REACTNATIVE__) return 'react-native';
-    return 'node';
-}

+ 8 - 0
packages/nodes/src/shared/docs.ts

@@ -0,0 +1,8 @@
+import type { Docs } from '@kinobi-so/node-types';
+
+export type DocsInput = string[] | string;
+
+export function parseDocs(docs: DocsInput | null | undefined): Docs {
+    if (docs === null || docs === undefined) return [];
+    return Array.isArray(docs) ? docs : [docs];
+}

+ 2 - 0
packages/nodes/src/shared/index.ts

@@ -0,0 +1,2 @@
+export * from './docs';
+export * from './stringCases';

+ 39 - 0
packages/nodes/src/shared/stringCases.ts

@@ -0,0 +1,39 @@
+import type {
+    CamelCaseString,
+    KebabCaseString,
+    PascalCaseString,
+    SnakeCaseString,
+    TitleCaseString,
+} from '@kinobi-so/node-types';
+
+export function capitalize(str: string): string {
+    if (str.length === 0) return str;
+    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
+}
+
+export function titleCase(str: string): TitleCaseString {
+    return str
+        .replace(/([A-Z])/g, ' $1')
+        .split(/[-_\s+.]/)
+        .filter(word => word.length > 0)
+        .map(capitalize)
+        .join(' ') as TitleCaseString;
+}
+
+export function pascalCase(str: string): PascalCaseString {
+    return titleCase(str).split(' ').join('') as PascalCaseString;
+}
+
+export function camelCase(str: string): CamelCaseString {
+    if (str.length === 0) return str as CamelCaseString;
+    const pascalStr = pascalCase(str);
+    return (pascalStr.charAt(0).toLowerCase() + pascalStr.slice(1)) as CamelCaseString;
+}
+
+export function kebabCase(str: string): KebabCaseString {
+    return titleCase(str).split(' ').join('-').toLowerCase() as KebabCaseString;
+}
+
+export function snakeCase(str: string): SnakeCaseString {
+    return titleCase(str).split(' ').join('_').toLowerCase() as SnakeCaseString;
+}

+ 18 - 0
packages/nodes/src/typeNodes/AmountTypeNode.ts

@@ -0,0 +1,18 @@
+import type { AmountTypeNode, NestedTypeNode, NumberTypeNode } from '@kinobi-so/node-types';
+
+export function amountTypeNode<TNumber extends NestedTypeNode<NumberTypeNode>>(
+    number: TNumber,
+    decimals: number,
+    unit?: string,
+): AmountTypeNode<TNumber> {
+    return Object.freeze({
+        kind: 'amountTypeNode',
+
+        // Data.
+        decimals,
+        ...(unit !== undefined && { unit }),
+
+        // Children.
+        number,
+    });
+}

+ 14 - 0
packages/nodes/src/typeNodes/ArrayTypeNode.ts

@@ -0,0 +1,14 @@
+import type { ArrayTypeNode, CountNode, TypeNode } from '@kinobi-so/node-types';
+
+export function arrayTypeNode<TItem extends TypeNode, TCount extends CountNode>(
+    item: TItem,
+    count: TCount,
+): ArrayTypeNode<TItem, TCount> {
+    return Object.freeze({
+        kind: 'arrayTypeNode',
+
+        // Children.
+        item,
+        count,
+    });
+}

+ 14 - 0
packages/nodes/src/typeNodes/BooleanTypeNode.ts

@@ -0,0 +1,14 @@
+import type { BooleanTypeNode, NestedTypeNode, NumberTypeNode } from '@kinobi-so/node-types';
+
+import { numberTypeNode } from './NumberTypeNode';
+
+export function booleanTypeNode<TSize extends NestedTypeNode<NumberTypeNode> = NumberTypeNode<'u8'>>(
+    size?: TSize,
+): BooleanTypeNode<TSize> {
+    return Object.freeze({
+        kind: 'booleanTypeNode',
+
+        // Children.
+        size: (size ?? numberTypeNode('u8')) as TSize,
+    });
+}

+ 5 - 0
packages/nodes/src/typeNodes/BytesTypeNode.ts

@@ -0,0 +1,5 @@
+import type { BytesTypeNode } from '@kinobi-so/node-types';
+
+export function bytesTypeNode(): BytesTypeNode {
+    return Object.freeze({ kind: 'bytesTypeNode' });
+}

+ 12 - 0
packages/nodes/src/typeNodes/DateTimeTypeNode.ts

@@ -0,0 +1,12 @@
+import type { DateTimeTypeNode, NestedTypeNode, NumberTypeNode } from '@kinobi-so/node-types';
+
+export function dateTimeTypeNode<TNumber extends NestedTypeNode<NumberTypeNode> = NestedTypeNode<NumberTypeNode>>(
+    number: TNumber,
+): DateTimeTypeNode<TNumber> {
+    return Object.freeze({
+        kind: 'dateTimeTypeNode',
+
+        // Children.
+        number,
+    });
+}

+ 18 - 0
packages/nodes/src/typeNodes/EnumEmptyVariantTypeNode.ts

@@ -0,0 +1,18 @@
+import type { EnumEmptyVariantTypeNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function enumEmptyVariantTypeNode(name: string, discriminator?: number): EnumEmptyVariantTypeNode {
+    if (!name) {
+        // TODO: Coded error.
+        throw new Error('EnumEmptyVariantTypeNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'enumEmptyVariantTypeNode',
+
+        // Data.
+        name: camelCase(name),
+        discriminator,
+    });
+}

+ 25 - 0
packages/nodes/src/typeNodes/EnumStructVariantTypeNode.ts

@@ -0,0 +1,25 @@
+import type { EnumStructVariantTypeNode, NestedTypeNode, StructTypeNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function enumStructVariantTypeNode<TStruct extends NestedTypeNode<StructTypeNode>>(
+    name: string,
+    struct: TStruct,
+    discriminator?: number,
+): EnumStructVariantTypeNode<TStruct> {
+    if (!name) {
+        // TODO: Coded error.
+        throw new Error('EnumStructVariantTypeNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'enumStructVariantTypeNode',
+
+        // Data.
+        name: camelCase(name),
+        ...(discriminator !== undefined && { discriminator }),
+
+        // Children.
+        struct,
+    });
+}

+ 25 - 0
packages/nodes/src/typeNodes/EnumTupleVariantTypeNode.ts

@@ -0,0 +1,25 @@
+import type { EnumTupleVariantTypeNode, NestedTypeNode, TupleTypeNode } from '@kinobi-so/node-types';
+
+import { camelCase } from '../shared';
+
+export function enumTupleVariantTypeNode<TTuple extends NestedTypeNode<TupleTypeNode>>(
+    name: string,
+    tuple: TTuple,
+    discriminator?: number,
+): EnumTupleVariantTypeNode<TTuple> {
+    if (!name) {
+        // TODO: Coded error.
+        throw new Error('EnumTupleVariantTypeNode must have a name.');
+    }
+
+    return Object.freeze({
+        kind: 'enumTupleVariantTypeNode',
+
+        // Data.
+        name: camelCase(name),
+        ...(discriminator !== undefined && { discriminator }),
+
+        // Children.
+        tuple,
+    });
+}

+ 24 - 0
packages/nodes/src/typeNodes/EnumTypeNode.ts

@@ -0,0 +1,24 @@
+import type { EnumTypeNode, EnumVariantTypeNode, NestedTypeNode, NumberTypeNode } from '@kinobi-so/node-types';
+
+import { numberTypeNode } from './NumberTypeNode';
+
+export function enumTypeNode<
+    const TVariants extends EnumVariantTypeNode[],
+    TSize extends NestedTypeNode<NumberTypeNode> = NumberTypeNode<'u8'>,
+>(variants: TVariants, options: { size?: TSize } = {}): EnumTypeNode<TVariants, TSize> {
+    return Object.freeze({
+        kind: 'enumTypeNode',
+
+        // Children.
+        variants,
+        size: (options.size ?? numberTypeNode('u8')) as TSize,
+    });
+}
+
+export function isScalarEnum(node: EnumTypeNode): boolean {
+    return node.variants.every(variant => variant.kind === 'enumEmptyVariantTypeNode');
+}
+
+export function isDataEnum(node: EnumTypeNode): boolean {
+    return !isScalarEnum(node);
+}

+ 5 - 0
packages/nodes/src/typeNodes/EnumVariantTypeNode.ts

@@ -0,0 +1,5 @@
+export const ENUM_VARIANT_TYPE_NODES = [
+    'enumEmptyVariantTypeNode' as const,
+    'enumStructVariantTypeNode' as const,
+    'enumTupleVariantTypeNode' as const,
+];

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů