codama-renderers-dart 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.browser.cjs +634 -159
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +635 -161
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +634 -159
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +635 -161
- package/dist/index.node.js.map +1 -1
- package/dist/index.react-native.js +635 -161
- package/dist/index.react-native.js.map +1 -1
- package/dist/types/fragments/accountPage.d.ts.map +1 -1
- package/dist/types/fragments/instructionPage.d.ts.map +1 -1
- package/dist/types/fragments/programPage.d.ts +1 -1
- package/dist/types/fragments/programPage.d.ts.map +1 -1
- package/dist/types/fragments/typePage.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/discriminators.d.ts +10 -0
- package/dist/types/utils/discriminators.d.ts.map +1 -0
- package/dist/types/utils/exactDecoder.d.ts +17 -0
- package/dist/types/utils/exactDecoder.d.ts.map +1 -0
- package/dist/types/utils/formatCode.d.ts +1 -1
- package/dist/types/utils/formatCode.d.ts.map +1 -1
- package/dist/types/utils/importMap.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +4 -0
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/nameTransformers.d.ts +1 -1
- package/dist/types/utils/nameTransformers.d.ts.map +1 -1
- package/dist/types/utils/normalizeRootNode.d.ts +11 -0
- package/dist/types/utils/normalizeRootNode.d.ts.map +1 -0
- package/dist/types/utils/valueNodes.d.ts +16 -0
- package/dist/types/utils/valueNodes.d.ts.map +1 -0
- package/dist/types/visitors/getTypeManifestVisitor.d.ts +1 -1
- package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
- package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
- package/package.json +31 -23
- package/readme.md +30 -3
- package/src/fragments/accountPage.ts +90 -17
- package/src/fragments/instructionPage.ts +109 -72
- package/src/fragments/programPage.ts +165 -6
- package/src/fragments/typePage.ts +36 -15
- package/src/index.ts +1 -0
- package/src/utils/discriminators.ts +121 -0
- package/src/utils/exactDecoder.ts +82 -0
- package/src/utils/formatCode.ts +7 -11
- package/src/utils/importMap.ts +16 -3
- package/src/utils/index.ts +4 -0
- package/src/utils/nameTransformers.ts +3 -2
- package/src/utils/normalizeRootNode.ts +76 -0
- package/src/utils/valueNodes.ts +87 -0
- package/src/utils/wellKnownAddresses.ts +4 -4
- package/src/visitors/getRenderMapVisitor.ts +1 -1
- package/src/visitors/getTypeManifestVisitor.ts +48 -29
- package/src/visitors/renderVisitor.ts +11 -4
|
@@ -7,16 +7,20 @@ import { visit } from "@codama/visitors-core";
|
|
|
7
7
|
|
|
8
8
|
import type { Fragment } from "../utils/fragment.js";
|
|
9
9
|
import {
|
|
10
|
-
emptyFragment,
|
|
11
10
|
fragment,
|
|
12
11
|
fragmentFromString,
|
|
13
12
|
mergeFragments,
|
|
14
13
|
use,
|
|
15
14
|
} from "../utils/fragment.js";
|
|
16
15
|
import type { RenderScope } from "../utils/options.js";
|
|
17
|
-
import { camelCase
|
|
16
|
+
import { camelCase } from "../utils/nameTransformers.js";
|
|
17
|
+
import { getDiscriminatorValidationFragment } from "../utils/discriminators.js";
|
|
18
|
+
import { getTopLevelDecoderFragment } from "../utils/exactDecoder.js";
|
|
19
|
+
import {
|
|
20
|
+
getDartValueFragment,
|
|
21
|
+
isConstDartValueNode,
|
|
22
|
+
} from "../utils/valueNodes.js";
|
|
18
23
|
import { getDiscriminatorConstantsFragment } from "./discriminatorConstants.js";
|
|
19
|
-
import { WELL_KNOWN_ADDRESSES } from "../utils/wellKnownAddresses.js";
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Generate a full Dart file for an instruction.
|
|
@@ -31,19 +35,55 @@ export function getInstructionPageFragment(
|
|
|
31
35
|
const parseFnName = scope.nameApi.instructionParseFunction(name);
|
|
32
36
|
|
|
33
37
|
const accounts = node.accounts ?? [];
|
|
34
|
-
const args = (node.arguments ?? []).filter(
|
|
35
|
-
(arg) => !isDiscriminatorArg(arg, node),
|
|
36
|
-
);
|
|
37
38
|
const allArgs = node.arguments ?? [];
|
|
39
|
+
const optionalAccountStrategy: string =
|
|
40
|
+
node.optionalAccountStrategy ?? "programId";
|
|
41
|
+
if (
|
|
42
|
+
optionalAccountStrategy !== "programId" &&
|
|
43
|
+
optionalAccountStrategy !== "omitted"
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Unsupported optional account strategy on instruction "${node.name}": ${optionalAccountStrategy}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const args = allArgs.filter(
|
|
50
|
+
(arg) =>
|
|
51
|
+
!isDiscriminatorArg(arg, node) && !isOmittedDefaultArgument(arg),
|
|
52
|
+
);
|
|
38
53
|
|
|
39
54
|
// Build the instruction data class
|
|
40
55
|
const dataClassName = `${typeName}InstructionData`;
|
|
41
56
|
|
|
42
57
|
// Visit each arg type once and collect manifests for reuse
|
|
43
|
-
const allArgManifests =
|
|
58
|
+
const allArgManifests = (node.arguments ?? []).map((arg) => ({
|
|
44
59
|
arg,
|
|
45
60
|
manifest: visit(arg.type, scope.typeManifestVisitor),
|
|
46
61
|
}));
|
|
62
|
+
const argDefaultValues = new Map(
|
|
63
|
+
allArgManifests.flatMap(({ arg, manifest }) => {
|
|
64
|
+
if (arg.defaultValue == null) return [];
|
|
65
|
+
const needsRenderedDefault =
|
|
66
|
+
isOmittedDefaultArgument(arg) ||
|
|
67
|
+
isDiscriminatorArg(arg, node) ||
|
|
68
|
+
arg.defaultValue.kind !== "accountBumpValueNode";
|
|
69
|
+
if (!needsRenderedDefault) return [];
|
|
70
|
+
return [
|
|
71
|
+
[
|
|
72
|
+
arg,
|
|
73
|
+
getDartValueFragment(arg.defaultValue, manifest.type.content),
|
|
74
|
+
] as const,
|
|
75
|
+
];
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
for (const arg of allArgs.filter((candidate) =>
|
|
79
|
+
isDiscriminatorArg(candidate, node),
|
|
80
|
+
)) {
|
|
81
|
+
if (!argDefaultValues.has(arg)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Field discriminator "${arg.name}" on instruction "${node.name}" must reference a field with a default value.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
47
87
|
|
|
48
88
|
// Data fields (non-discriminator arguments)
|
|
49
89
|
const dataFieldDecls = allArgManifests
|
|
@@ -56,20 +96,25 @@ export function getInstructionPageFragment(
|
|
|
56
96
|
const dataCtorParams = allArgManifests
|
|
57
97
|
.map(({ arg, manifest }) => {
|
|
58
98
|
const fieldName = camelCase(arg.name as string);
|
|
99
|
+
if (isOmittedDefaultArgument(arg)) return "";
|
|
59
100
|
if (isDiscriminatorArg(arg, node)) {
|
|
60
101
|
return isConstDefaultValue(arg.defaultValue)
|
|
61
|
-
? ` this.${fieldName} = ${
|
|
102
|
+
? ` this.${fieldName} = ${argDefaultValues.get(arg)!.content},`
|
|
62
103
|
: ` ${manifest.type.content}? ${fieldName},`;
|
|
63
104
|
}
|
|
64
105
|
return ` required this.${fieldName},`;
|
|
65
106
|
})
|
|
107
|
+
.filter(Boolean)
|
|
66
108
|
.join("\n");
|
|
67
109
|
|
|
68
110
|
const dataCtorInitializers = allArgManifests
|
|
69
111
|
.map(({ arg }) => {
|
|
70
112
|
const fieldName = camelCase(arg.name as string);
|
|
113
|
+
if (isOmittedDefaultArgument(arg)) {
|
|
114
|
+
return ` ${fieldName} = ${argDefaultValues.get(arg)!.content}`;
|
|
115
|
+
}
|
|
71
116
|
if (isDiscriminatorArg(arg, node) && !isConstDefaultValue(arg.defaultValue)) {
|
|
72
|
-
return ` ${fieldName} = ${fieldName} ?? ${
|
|
117
|
+
return ` ${fieldName} = ${fieldName} ?? ${argDefaultValues.get(arg)!.content}`;
|
|
73
118
|
}
|
|
74
119
|
return null;
|
|
75
120
|
})
|
|
@@ -90,13 +135,17 @@ export function getInstructionPageFragment(
|
|
|
90
135
|
.join("\n");
|
|
91
136
|
|
|
92
137
|
const toMapFields = allArgs
|
|
93
|
-
.map(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
.map((arg) => {
|
|
139
|
+
const value =
|
|
140
|
+
isOmittedDefaultArgument(arg) || isDiscriminatorArg(arg, node)
|
|
141
|
+
? argDefaultValues.get(arg)!.content
|
|
142
|
+
: `value.${camelCase(arg.name as string)}`;
|
|
143
|
+
return ` '${arg.name as string}': ${value},`;
|
|
144
|
+
})
|
|
97
145
|
.join("\n");
|
|
98
146
|
|
|
99
147
|
const fromMapFields = allArgManifests
|
|
148
|
+
.filter(({ arg }) => !isOmittedDefaultArgument(arg))
|
|
100
149
|
.map(({ arg, manifest }) => {
|
|
101
150
|
const typeStr = manifest.type.content;
|
|
102
151
|
const isNullable = typeStr.endsWith("?");
|
|
@@ -105,7 +154,20 @@ export function getInstructionPageFragment(
|
|
|
105
154
|
})
|
|
106
155
|
.join("\n");
|
|
107
156
|
|
|
108
|
-
const dataCtorKeyword =
|
|
157
|
+
const dataCtorKeyword = allArgs.every(
|
|
158
|
+
(arg) =>
|
|
159
|
+
!isOmittedDefaultArgument(arg) ||
|
|
160
|
+
isConstDartValueNode(arg.defaultValue!),
|
|
161
|
+
) && allArgs.every(
|
|
162
|
+
(arg) =>
|
|
163
|
+
!isDiscriminatorArg(arg, node) ||
|
|
164
|
+
isConstDefaultValue(arg.defaultValue),
|
|
165
|
+
)
|
|
166
|
+
? "const "
|
|
167
|
+
: "";
|
|
168
|
+
const dataCtorSignature = dataCtorParams
|
|
169
|
+
? `${dataCtorKeyword}${dataClassName}({\n${dataCtorParams}\n })${dataCtorInitializers ? ` :\n${dataCtorInitializers}` : ""};`
|
|
170
|
+
: `${dataCtorKeyword}${dataClassName}()${dataCtorInitializers ? ` :\n${dataCtorInitializers}` : ""};`;
|
|
109
171
|
const dataEncoderName = `get${typeName}InstructionDataEncoder`;
|
|
110
172
|
const dataDecoderName = `get${typeName}InstructionDataDecoder`;
|
|
111
173
|
const dataCodecName = `get${typeName}InstructionDataCodec`;
|
|
@@ -134,7 +196,11 @@ export function getInstructionPageFragment(
|
|
|
134
196
|
const manifest = argManifestMap.get(arg)!;
|
|
135
197
|
const fieldName = camelCase(arg.name as string);
|
|
136
198
|
const typeStr = manifest.type.content;
|
|
137
|
-
|
|
199
|
+
// Account bumps require asynchronous PDA derivation, so callers must
|
|
200
|
+
// provide them instead of relying on a synchronous generated default.
|
|
201
|
+
const hasDefault =
|
|
202
|
+
arg.defaultValue != null &&
|
|
203
|
+
arg.defaultValue.kind !== "accountBumpValueNode";
|
|
138
204
|
if (hasDefault) {
|
|
139
205
|
// Don't add `?` if the type is already nullable.
|
|
140
206
|
const nullableType = typeStr.endsWith("?") ? typeStr : `${typeStr}?`;
|
|
@@ -151,7 +217,10 @@ export function getInstructionPageFragment(
|
|
|
151
217
|
const role = getAccountRole(acc);
|
|
152
218
|
const isOptional = acc.isOptional ?? false;
|
|
153
219
|
if (isOptional) {
|
|
154
|
-
|
|
220
|
+
if (optionalAccountStrategy === "omitted") {
|
|
221
|
+
return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}),`;
|
|
222
|
+
}
|
|
223
|
+
return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}) else AccountMeta(address: ${instrProgramParam}, role: AccountRole.readonly),`;
|
|
155
224
|
}
|
|
156
225
|
return ` AccountMeta(address: ${fieldName}, role: ${role}),`;
|
|
157
226
|
})
|
|
@@ -161,17 +230,31 @@ export function getInstructionPageFragment(
|
|
|
161
230
|
const dataConstruction = allArgs
|
|
162
231
|
.map((arg) => {
|
|
163
232
|
const fieldName = camelCase(arg.name as string);
|
|
164
|
-
if (
|
|
233
|
+
if (
|
|
234
|
+
isOmittedDefaultArgument(arg) ||
|
|
235
|
+
isDiscriminatorArg(arg, node)
|
|
236
|
+
) {
|
|
165
237
|
return ""; // Use default
|
|
166
238
|
}
|
|
167
|
-
|
|
168
|
-
return ` ${fieldName}: ${fieldName}${arg.defaultValue != null ? ` ?? ${
|
|
239
|
+
const skipDefault = arg.defaultValue?.kind === "accountBumpValueNode";
|
|
240
|
+
return ` ${fieldName}: ${fieldName}${arg.defaultValue != null && !skipDefault ? ` ?? ${argDefaultValues.get(arg)!.content}` : ""},`;
|
|
169
241
|
})
|
|
170
242
|
.filter(Boolean)
|
|
171
243
|
.join("\n");
|
|
172
244
|
|
|
173
245
|
// Discriminator
|
|
174
246
|
const discFragment = getDiscriminatorConstantsFragment(node, scope);
|
|
247
|
+
const discriminatorValidation = getDiscriminatorValidationFragment(
|
|
248
|
+
node,
|
|
249
|
+
scope,
|
|
250
|
+
);
|
|
251
|
+
const topLevelDecoder = getTopLevelDecoderFragment({
|
|
252
|
+
typeName: dataClassName,
|
|
253
|
+
description: `${name} instruction decoder`,
|
|
254
|
+
discriminatorValidation,
|
|
255
|
+
fromMapFields,
|
|
256
|
+
requireExactConsumption: true,
|
|
257
|
+
});
|
|
175
258
|
|
|
176
259
|
const parts: Fragment[] = [
|
|
177
260
|
fragment`// Auto-generated. Do not edit.
|
|
@@ -199,9 +282,7 @@ ${use("AccountRole", "solanaInstructions")}`,
|
|
|
199
282
|
parts.push(fragment`
|
|
200
283
|
@immutable
|
|
201
284
|
class ${fragmentFromString(dataClassName)} {
|
|
202
|
-
${fragmentFromString(
|
|
203
|
-
${fragmentFromString(dataCtorParams)}
|
|
204
|
-
})${fragmentFromString(dataCtorInitializers ? ` :\n${dataCtorInitializers}` : "")};
|
|
285
|
+
${fragmentFromString(dataCtorSignature)}
|
|
205
286
|
|
|
206
287
|
${fragmentFromString(dataFieldDecls)}
|
|
207
288
|
}`);
|
|
@@ -226,12 +307,7 @@ Decoder<${fragmentFromString(dataClassName)}> ${fragmentFromString(dataDecoderNa
|
|
|
226
307
|
${fragmentFromString(decFields)}
|
|
227
308
|
]);
|
|
228
309
|
|
|
229
|
-
|
|
230
|
-
structDecoder,
|
|
231
|
-
(Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(dataClassName)}(
|
|
232
|
-
${fragmentFromString(fromMapFields)}
|
|
233
|
-
),
|
|
234
|
-
);
|
|
310
|
+
${topLevelDecoder}
|
|
235
311
|
}
|
|
236
312
|
|
|
237
313
|
Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}> ${fragmentFromString(dataCodecName)}() {
|
|
@@ -274,6 +350,9 @@ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruct
|
|
|
274
350
|
result.imports.mergeWith(manifest.decoder.imports);
|
|
275
351
|
result.imports.mergeWith(manifest.type.imports);
|
|
276
352
|
}
|
|
353
|
+
for (const defaultValue of argDefaultValues.values()) {
|
|
354
|
+
result.imports.mergeWith(defaultValue.imports);
|
|
355
|
+
}
|
|
277
356
|
|
|
278
357
|
return result;
|
|
279
358
|
}
|
|
@@ -299,23 +378,6 @@ function isDiscriminatorArg(
|
|
|
299
378
|
);
|
|
300
379
|
}
|
|
301
380
|
|
|
302
|
-
function getDiscriminatorDefault(
|
|
303
|
-
arg: InstructionArgumentNode,
|
|
304
|
-
_node: InstructionNode,
|
|
305
|
-
): string {
|
|
306
|
-
if (arg.defaultValue) {
|
|
307
|
-
if (arg.defaultValue.kind === "numberValueNode") {
|
|
308
|
-
return String(arg.defaultValue.number);
|
|
309
|
-
}
|
|
310
|
-
if (arg.defaultValue.kind === "bytesValueNode") {
|
|
311
|
-
const data = arg.defaultValue.data as string;
|
|
312
|
-
const bytes = data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
|
|
313
|
-
return `Uint8List.fromList([${bytes.join(", ")}])`;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return "0";
|
|
317
|
-
}
|
|
318
|
-
|
|
319
381
|
function isConstDefaultValue(defaultValue: InstructionArgumentNode["defaultValue"]): boolean {
|
|
320
382
|
if (!defaultValue) return false;
|
|
321
383
|
switch (defaultValue.kind) {
|
|
@@ -329,31 +391,6 @@ function isConstDefaultValue(defaultValue: InstructionArgumentNode["defaultValue
|
|
|
329
391
|
}
|
|
330
392
|
}
|
|
331
393
|
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
const dv = arg.defaultValue;
|
|
335
|
-
switch (dv.kind) {
|
|
336
|
-
case "numberValueNode":
|
|
337
|
-
return String(dv.number);
|
|
338
|
-
case "booleanValueNode":
|
|
339
|
-
return String(dv.boolean);
|
|
340
|
-
case "stringValueNode":
|
|
341
|
-
return `'${dv.string}'`;
|
|
342
|
-
case "publicKeyValueNode": {
|
|
343
|
-
const wellKnownName = WELL_KNOWN_ADDRESSES.get(dv.publicKey);
|
|
344
|
-
if (wellKnownName) {
|
|
345
|
-
use(wellKnownName, "solanaAddresses");
|
|
346
|
-
return wellKnownName;
|
|
347
|
-
}
|
|
348
|
-
return `Address('${dv.publicKey}')`;
|
|
349
|
-
}
|
|
350
|
-
case "noneValueNode":
|
|
351
|
-
return "null";
|
|
352
|
-
case "bytesValueNode": {
|
|
353
|
-
const bytes = dv.data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
|
|
354
|
-
return `Uint8List.fromList([${bytes.join(", ")}])`;
|
|
355
|
-
}
|
|
356
|
-
default:
|
|
357
|
-
return "null";
|
|
358
|
-
}
|
|
394
|
+
function isOmittedDefaultArgument(arg: InstructionArgumentNode): boolean {
|
|
395
|
+
return arg.defaultValue != null && arg.defaultValueStrategy === "omitted";
|
|
359
396
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
getAllInstructionsWithSubs,
|
|
3
|
+
type InstructionNode,
|
|
4
|
+
type ProgramNode,
|
|
5
|
+
type ValueNode,
|
|
6
|
+
} from "@codama/nodes";
|
|
7
|
+
import { visit } from "@codama/visitors-core";
|
|
2
8
|
|
|
3
9
|
import type { Fragment } from "../utils/fragment.js";
|
|
4
10
|
import {
|
|
@@ -67,19 +73,172 @@ ${fragmentFromString(accountVariants)}
|
|
|
67
73
|
}`);
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
// Instruction
|
|
71
|
-
const instructions = node
|
|
76
|
+
// Instruction identification and parsing helpers.
|
|
77
|
+
const instructions = getAllInstructionsWithSubs(node);
|
|
72
78
|
if (instructions.length > 0) {
|
|
79
|
+
const programName = pascalCase(name);
|
|
80
|
+
const instructionEnum = `${programName}Instruction`;
|
|
73
81
|
const instrVariants = instructions
|
|
74
82
|
.map((instr) => ` ${camelCase(instr.name as string)},`)
|
|
75
83
|
.join("\n");
|
|
76
84
|
|
|
77
85
|
parts.push(fragment`
|
|
78
|
-
/// Known instructions for the ${fragmentFromString(
|
|
79
|
-
enum ${fragmentFromString(
|
|
86
|
+
/// Known instructions for the ${fragmentFromString(programName)} program.
|
|
87
|
+
enum ${fragmentFromString(instructionEnum)} {
|
|
80
88
|
${fragmentFromString(instrVariants)}
|
|
81
89
|
}`);
|
|
90
|
+
|
|
91
|
+
const identifiableInstructions = instructions
|
|
92
|
+
.map((instruction) => ({
|
|
93
|
+
instruction,
|
|
94
|
+
condition: getInstructionDiscriminatorCondition(instruction, scope),
|
|
95
|
+
}))
|
|
96
|
+
.filter(({ condition }) => condition !== null) as {
|
|
97
|
+
instruction: InstructionNode;
|
|
98
|
+
condition: Fragment;
|
|
99
|
+
}[];
|
|
100
|
+
|
|
101
|
+
if (identifiableInstructions.length > 0) {
|
|
102
|
+
const identifyBranches = identifiableInstructions.map(
|
|
103
|
+
({ instruction, condition }) => fragment` if (${condition}) {
|
|
104
|
+
return ${fragmentFromString(instructionEnum)}.${fragmentFromString(camelCase(instruction.name as string))};
|
|
105
|
+
}`,
|
|
106
|
+
);
|
|
107
|
+
const parsedBase = `Parsed${programName}Instruction`;
|
|
108
|
+
const parsedClasses = instructions.map((instruction) => {
|
|
109
|
+
const instructionName = pascalCase(instruction.name as string);
|
|
110
|
+
const variant = camelCase(instruction.name as string);
|
|
111
|
+
const parsedClass = `Parsed${instructionName}`;
|
|
112
|
+
const dataClass = `${instructionName}InstructionData`;
|
|
113
|
+
return fragment`/// A parsed ${fragmentFromString(instructionName)} instruction.
|
|
114
|
+
final class ${fragmentFromString(parsedClass)} extends ${fragmentFromString(parsedBase)} {
|
|
115
|
+
const ${fragmentFromString(parsedClass)}({required this.data})
|
|
116
|
+
: super(${fragmentFromString(instructionEnum)}.${fragmentFromString(variant)});
|
|
117
|
+
|
|
118
|
+
final ${use(dataClass, "../instructions/instructions.dart")} data;
|
|
119
|
+
}`;
|
|
120
|
+
});
|
|
121
|
+
const parseBranches = instructions.map((instruction) => {
|
|
122
|
+
const instructionName = pascalCase(instruction.name as string);
|
|
123
|
+
const variant = camelCase(instruction.name as string);
|
|
124
|
+
const parsedClass = `Parsed${instructionName}`;
|
|
125
|
+
const parseFunction = scope.nameApi.instructionParseFunction(
|
|
126
|
+
instruction.name as string,
|
|
127
|
+
);
|
|
128
|
+
return fragment` ${fragmentFromString(instructionEnum)}.${fragmentFromString(variant)} => ${fragmentFromString(parsedClass)}(
|
|
129
|
+
data: ${use(parseFunction, "../instructions/instructions.dart")}(instruction),
|
|
130
|
+
),`;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
parts.push(fragment`
|
|
134
|
+
/// Identifies the type of a ${fragmentFromString(programName)} instruction.
|
|
135
|
+
${fragmentFromString(instructionEnum)} identify${fragmentFromString(programName)}Instruction(
|
|
136
|
+
${use("Uint8List", "dartTypedData")} data,
|
|
137
|
+
) {
|
|
138
|
+
${mergeFragments(identifyBranches, (cs) => cs.join("\n"))}
|
|
139
|
+
|
|
140
|
+
throw ${use("SolanaError", "solanaErrors")}(
|
|
141
|
+
${use("SolanaErrorCode", "solanaErrors")}.programClientsFailedToIdentifyInstruction,
|
|
142
|
+
{
|
|
143
|
+
'instructionData': data,
|
|
144
|
+
'programName': '${fragmentFromString(name)}',
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// A parsed instruction from the ${fragmentFromString(programName)} program.
|
|
150
|
+
sealed class ${fragmentFromString(parsedBase)} {
|
|
151
|
+
const ${fragmentFromString(parsedBase)}(this.instructionType);
|
|
152
|
+
|
|
153
|
+
final ${fragmentFromString(instructionEnum)} instructionType;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
${mergeFragments(parsedClasses, (cs) => cs.join("\n\n"))}
|
|
157
|
+
|
|
158
|
+
/// Parses a ${fragmentFromString(programName)} instruction.
|
|
159
|
+
${fragmentFromString(parsedBase)} parse${fragmentFromString(programName)}Instruction(
|
|
160
|
+
${use("Instruction", "solanaInstructions")} instruction,
|
|
161
|
+
) {
|
|
162
|
+
return switch (identify${fragmentFromString(programName)}Instruction(
|
|
163
|
+
instruction.data ?? Uint8List(0),
|
|
164
|
+
)) {
|
|
165
|
+
${mergeFragments(parseBranches, (cs) => cs.join("\n"))}
|
|
166
|
+
};
|
|
167
|
+
}`);
|
|
168
|
+
}
|
|
82
169
|
}
|
|
83
170
|
|
|
84
171
|
return mergeFragments(parts, (cs) => cs.join("\n"));
|
|
85
|
-
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getInstructionDiscriminatorCondition(
|
|
175
|
+
instruction: InstructionNode,
|
|
176
|
+
scope: RenderScope,
|
|
177
|
+
): Fragment | null {
|
|
178
|
+
const discriminators = instruction.discriminators ?? [];
|
|
179
|
+
if (discriminators.length === 0) return null;
|
|
180
|
+
|
|
181
|
+
const conditions = discriminators.map((discriminator): Fragment | null => {
|
|
182
|
+
switch (discriminator.kind) {
|
|
183
|
+
case "sizeDiscriminatorNode":
|
|
184
|
+
return fragment`data.length == ${discriminator.size}`;
|
|
185
|
+
case "constantDiscriminatorNode": {
|
|
186
|
+
const manifest = visit(
|
|
187
|
+
discriminator.constant.type,
|
|
188
|
+
scope.typeManifestVisitor,
|
|
189
|
+
);
|
|
190
|
+
const value = getValueFragment(
|
|
191
|
+
discriminator.constant.value,
|
|
192
|
+
manifest.type.content,
|
|
193
|
+
);
|
|
194
|
+
if (value === null) return null;
|
|
195
|
+
return fragment`${use("containsBytes", "solanaCodecsCore")}(data, ${manifest.encoder}.encode(${value}), ${discriminator.offset})`;
|
|
196
|
+
}
|
|
197
|
+
case "fieldDiscriminatorNode": {
|
|
198
|
+
const argument = (instruction.arguments ?? []).find(
|
|
199
|
+
(candidate) => candidate.name === discriminator.name,
|
|
200
|
+
);
|
|
201
|
+
if (argument?.defaultValue == null) return null;
|
|
202
|
+
const manifest = visit(argument.type, scope.typeManifestVisitor);
|
|
203
|
+
const value = getValueFragment(
|
|
204
|
+
argument.defaultValue,
|
|
205
|
+
manifest.type.content,
|
|
206
|
+
);
|
|
207
|
+
if (value === null) return null;
|
|
208
|
+
return fragment`${use("containsBytes", "solanaCodecsCore")}(data, ${manifest.encoder}.encode(${value}), ${discriminator.offset})`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (conditions.some((condition) => condition === null)) return null;
|
|
214
|
+
return mergeFragments(conditions as Fragment[], (cs) => cs.join(" && "));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getValueFragment(
|
|
218
|
+
value:
|
|
219
|
+
| ValueNode
|
|
220
|
+
| NonNullable<
|
|
221
|
+
NonNullable<InstructionNode["arguments"]>[number]["defaultValue"]
|
|
222
|
+
>,
|
|
223
|
+
dartType?: string,
|
|
224
|
+
): Fragment | null {
|
|
225
|
+
switch (value.kind) {
|
|
226
|
+
case "numberValueNode":
|
|
227
|
+
return fragmentFromString(
|
|
228
|
+
dartType === "BigInt"
|
|
229
|
+
? `BigInt.from(${value.number})`
|
|
230
|
+
: String(value.number),
|
|
231
|
+
);
|
|
232
|
+
case "booleanValueNode":
|
|
233
|
+
return fragmentFromString(String(value.boolean));
|
|
234
|
+
case "stringValueNode":
|
|
235
|
+
return fragmentFromString(`'${value.string.replaceAll("'", "\\'")}'`);
|
|
236
|
+
case "bytesValueNode": {
|
|
237
|
+
const clean = value.data.replace(/^0x/, "");
|
|
238
|
+
const bytes = clean.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [];
|
|
239
|
+
return fragment`${use("Uint8List", "dartTypedData")}.fromList([${fragmentFromString(bytes.join(", "))}])`;
|
|
240
|
+
}
|
|
241
|
+
default:
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DefinedTypeNode,
|
|
3
|
+
EnumVariantTypeNode,
|
|
4
|
+
StructFieldTypeNode,
|
|
5
|
+
TypeNode,
|
|
6
|
+
} from "@codama/nodes";
|
|
2
7
|
import { resolveNestedTypeNode } from "@codama/nodes";
|
|
3
8
|
import { visit } from "@codama/visitors-core";
|
|
4
9
|
|
|
@@ -30,13 +35,14 @@ export function getTypePageFragment(
|
|
|
30
35
|
|
|
31
36
|
// Check what kind of type this is
|
|
32
37
|
if (typeNode.kind === "enumTypeNode") {
|
|
33
|
-
const
|
|
38
|
+
const variants = typeNode.variants ?? [];
|
|
39
|
+
const isScalar = variants.every(
|
|
34
40
|
(v) => v.kind === "enumEmptyVariantTypeNode",
|
|
35
41
|
);
|
|
36
42
|
if (isScalar) {
|
|
37
|
-
return getScalarEnumPageFragment(node, scope);
|
|
43
|
+
return getScalarEnumPageFragment(node, variants, scope);
|
|
38
44
|
}
|
|
39
|
-
return getDataEnumPageFragment(node, scope);
|
|
45
|
+
return getDataEnumPageFragment(node, variants, scope);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
if (typeNode.kind === "structTypeNode") {
|
|
@@ -52,6 +58,7 @@ export function getTypePageFragment(
|
|
|
52
58
|
*/
|
|
53
59
|
function getScalarEnumPageFragment(
|
|
54
60
|
node: DefinedTypeNode,
|
|
61
|
+
enumVariants: EnumVariantTypeNode[],
|
|
55
62
|
scope: RenderScope,
|
|
56
63
|
): Fragment {
|
|
57
64
|
const name = node.name as string;
|
|
@@ -59,7 +66,7 @@ function getScalarEnumPageFragment(
|
|
|
59
66
|
const enumNode = node.type;
|
|
60
67
|
if (enumNode.kind !== "enumTypeNode") return emptyFragment();
|
|
61
68
|
|
|
62
|
-
const variants =
|
|
69
|
+
const variants = enumVariants.map((v) => {
|
|
63
70
|
const variantName = scope.nameApi.enumVariant(v.name as string);
|
|
64
71
|
return variantName;
|
|
65
72
|
});
|
|
@@ -73,6 +80,18 @@ function getScalarEnumPageFragment(
|
|
|
73
80
|
const resolvedSize = resolveNestedTypeNode(enumNode.size);
|
|
74
81
|
const sizeType = resolvedSize.format ?? "u8";
|
|
75
82
|
const sizeCodecName = getNumberCodecName(sizeType);
|
|
83
|
+
const isU64 = sizeType === "u64";
|
|
84
|
+
const encodedVariantIndex = isU64
|
|
85
|
+
? "BigInt.from(value.index)"
|
|
86
|
+
: "value.index";
|
|
87
|
+
const decodedVariant = isU64
|
|
88
|
+
? `(BigInt value, Uint8List bytes, int offset) {
|
|
89
|
+
if (value.isNegative || value >= BigInt.from(${typeName}.values.length)) {
|
|
90
|
+
throw RangeError('Invalid ${typeName} discriminator: ' + value.toString());
|
|
91
|
+
}
|
|
92
|
+
return ${typeName}.values[value.toInt()];
|
|
93
|
+
}`
|
|
94
|
+
: `(int value, Uint8List bytes, int offset) => ${typeName}.values[value]`;
|
|
76
95
|
|
|
77
96
|
return fragment`// Auto-generated. Do not edit.
|
|
78
97
|
// ignore_for_file: type=lint
|
|
@@ -94,14 +113,14 @@ ${fragmentFromString(variantLines)}
|
|
|
94
113
|
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
95
114
|
return transformEncoder(
|
|
96
115
|
get${fragmentFromString(sizeCodecName)}Encoder(),
|
|
97
|
-
(${fragmentFromString(typeName)} value) =>
|
|
116
|
+
(${fragmentFromString(typeName)} value) => ${fragmentFromString(encodedVariantIndex)},
|
|
98
117
|
);
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
102
121
|
return transformDecoder(
|
|
103
122
|
get${fragmentFromString(sizeCodecName)}Decoder(),
|
|
104
|
-
|
|
123
|
+
${fragmentFromString(decodedVariant)},
|
|
105
124
|
);
|
|
106
125
|
}
|
|
107
126
|
|
|
@@ -115,6 +134,7 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
|
|
|
115
134
|
*/
|
|
116
135
|
function getDataEnumPageFragment(
|
|
117
136
|
node: DefinedTypeNode,
|
|
137
|
+
variants: EnumVariantTypeNode[],
|
|
118
138
|
scope: RenderScope,
|
|
119
139
|
): Fragment {
|
|
120
140
|
const name = node.name as string;
|
|
@@ -130,8 +150,9 @@ function getDataEnumPageFragment(
|
|
|
130
150
|
// Collect all field type manifests from variants so we can merge their imports
|
|
131
151
|
const allVariantManifests: { encoder: Fragment; decoder: Fragment; type: Fragment }[] = [];
|
|
132
152
|
|
|
133
|
-
for (let i = 0; i <
|
|
134
|
-
const variant =
|
|
153
|
+
for (let i = 0; i < variants.length; i++) {
|
|
154
|
+
const variant = variants[i];
|
|
155
|
+
const variantName = pascalCase(variant.name as string);
|
|
135
156
|
const variantClassName = scope.nameApi.sealedClassVariant(
|
|
136
157
|
name,
|
|
137
158
|
variant.name as string,
|
|
@@ -149,7 +170,7 @@ function getDataEnumPageFragment(
|
|
|
149
170
|
int get hashCode => runtimeType.hashCode;
|
|
150
171
|
|
|
151
172
|
@override
|
|
152
|
-
String toString() => '${typeName}.${
|
|
173
|
+
String toString() => '${typeName}.${variantName}()';
|
|
153
174
|
}`);
|
|
154
175
|
|
|
155
176
|
encoderVariants.push(
|
|
@@ -162,7 +183,7 @@ function getDataEnumPageFragment(
|
|
|
162
183
|
decodeCases.push(`case ${i}: return const ${variantClassName}();`);
|
|
163
184
|
} else if (variant.kind === "enumStructVariantTypeNode") {
|
|
164
185
|
const resolvedStruct = resolveNestedTypeNode(variant.struct);
|
|
165
|
-
const fields = resolvedStruct.fields;
|
|
186
|
+
const fields = resolvedStruct.fields ?? [];
|
|
166
187
|
|
|
167
188
|
// Visit each field type once and collect manifests
|
|
168
189
|
const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
|
|
@@ -217,7 +238,7 @@ ${fieldDecls}
|
|
|
217
238
|
int get hashCode => ${hashExpression};
|
|
218
239
|
|
|
219
240
|
@override
|
|
220
|
-
String toString() => '${typeName}.${
|
|
241
|
+
String toString() => '${typeName}.${variantName}(${toStringFields})';
|
|
221
242
|
}`);
|
|
222
243
|
|
|
223
244
|
const encFields = fieldManifests
|
|
@@ -270,7 +291,7 @@ ${fieldDecls}
|
|
|
270
291
|
decodeCases.push(`case ${i}: return ${variantClassName}(${fromMapFields.replace(/\n/g, ' ').replace(/,$/, '')});`);
|
|
271
292
|
} else if (variant.kind === "enumTupleVariantTypeNode") {
|
|
272
293
|
const resolvedTuple = resolveNestedTypeNode(variant.tuple);
|
|
273
|
-
const items = resolvedTuple.items;
|
|
294
|
+
const items = resolvedTuple.items ?? [];
|
|
274
295
|
if (items.length === 1) {
|
|
275
296
|
const manifest = visit(items[0], scope.typeManifestVisitor);
|
|
276
297
|
allVariantManifests.push(manifest);
|
|
@@ -288,7 +309,7 @@ ${fieldDecls}
|
|
|
288
309
|
int get hashCode => value.hashCode;
|
|
289
310
|
|
|
290
311
|
@override
|
|
291
|
-
String toString() => '${typeName}.${
|
|
312
|
+
String toString() => '${typeName}.${variantName}(\$value)';
|
|
292
313
|
}`);
|
|
293
314
|
encoderVariants.push(
|
|
294
315
|
`(${i}, transformEncoder<${manifest.type.content}, Map<String, Object?>>(${manifest.encoder.content}, (Map<String, Object?> map) => map['value']! as ${manifest.type.content}))`,
|
|
@@ -383,7 +404,7 @@ function getStructPageFragment(
|
|
|
383
404
|
const structNode = node.type;
|
|
384
405
|
if (structNode.kind !== "structTypeNode") return emptyFragment();
|
|
385
406
|
|
|
386
|
-
const fields = structNode.fields;
|
|
407
|
+
const fields = structNode.fields ?? [];
|
|
387
408
|
|
|
388
409
|
// Visit each field type once and collect manifests for reuse
|
|
389
410
|
const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { getRenderMapVisitor } from "./visitors/getRenderMapVisitor.js";
|
|
|
3
3
|
export { getTypeManifestVisitor } from "./visitors/getTypeManifestVisitor.js";
|
|
4
4
|
export { DartImportMap, DART_EXTERNAL_PACKAGE_MAP } from "./utils/importMap.js";
|
|
5
5
|
export { createDartNameApi } from "./utils/nameTransformers.js";
|
|
6
|
+
export { normalizeRootNode } from "./utils/normalizeRootNode.js";
|
|
6
7
|
export type { DartNameApi } from "./utils/nameTransformers.js";
|
|
7
8
|
export type { RenderOptions, GetRenderMapOptions, RenderScope } from "./utils/options.js";
|
|
8
9
|
export type { TypeManifest } from "./utils/typeManifest.js";
|