codama-renderers-dart 0.2.0
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/package.json +61 -0
- package/readme.md +297 -0
- package/src/fragments/accountPage.ts +201 -0
- package/src/fragments/discriminatorConstants.ts +74 -0
- package/src/fragments/errorPage.ts +82 -0
- package/src/fragments/index.ts +8 -0
- package/src/fragments/indexPage.ts +44 -0
- package/src/fragments/instructionPage.ts +306 -0
- package/src/fragments/pdaPage.ts +128 -0
- package/src/fragments/programPage.ts +63 -0
- package/src/fragments/typePage.ts +554 -0
- package/src/index.ts +13 -0
- package/src/types/global.d.ts +2 -0
- package/src/utils/codecs.ts +19 -0
- package/src/utils/formatCode.ts +35 -0
- package/src/utils/fragment.ts +83 -0
- package/src/utils/importMap.ts +129 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/nameTransformers.ts +112 -0
- package/src/utils/options.ts +46 -0
- package/src/utils/typeManifest.ts +31 -0
- package/src/visitors/getRenderMapVisitor.ts +272 -0
- package/src/visitors/getTypeManifestVisitor.ts +794 -0
- package/src/visitors/index.ts +3 -0
- package/src/visitors/renderVisitor.ts +154 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import type { DefinedTypeNode, StructFieldTypeNode, TypeNode } from "@codama/nodes";
|
|
2
|
+
import { resolveNestedTypeNode } from "@codama/nodes";
|
|
3
|
+
import { visit } from "@codama/visitors-core";
|
|
4
|
+
|
|
5
|
+
import type { Fragment } from "../utils/fragment.js";
|
|
6
|
+
import {
|
|
7
|
+
emptyFragment,
|
|
8
|
+
fragment,
|
|
9
|
+
fragmentFromString,
|
|
10
|
+
mergeFragments,
|
|
11
|
+
use,
|
|
12
|
+
} from "../utils/fragment.js";
|
|
13
|
+
import type { RenderScope } from "../utils/options.js";
|
|
14
|
+
import {
|
|
15
|
+
camelCase,
|
|
16
|
+
pascalCase,
|
|
17
|
+
snakeCase,
|
|
18
|
+
} from "../utils/nameTransformers.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a full Dart file for a defined type.
|
|
22
|
+
*/
|
|
23
|
+
export function getTypePageFragment(
|
|
24
|
+
node: DefinedTypeNode,
|
|
25
|
+
scope: RenderScope,
|
|
26
|
+
): Fragment {
|
|
27
|
+
const name = node.name as string;
|
|
28
|
+
const typeName = scope.nameApi.dataType(name);
|
|
29
|
+
const typeNode = node.type;
|
|
30
|
+
|
|
31
|
+
// Check what kind of type this is
|
|
32
|
+
if (typeNode.kind === "enumTypeNode") {
|
|
33
|
+
const isScalar = typeNode.variants.every(
|
|
34
|
+
(v) => v.kind === "enumEmptyVariantTypeNode",
|
|
35
|
+
);
|
|
36
|
+
if (isScalar) {
|
|
37
|
+
return getScalarEnumPageFragment(node, scope);
|
|
38
|
+
}
|
|
39
|
+
return getDataEnumPageFragment(node, scope);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeNode.kind === "structTypeNode") {
|
|
43
|
+
return getStructPageFragment(node, scope);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Simple type alias — just create codec wrappers
|
|
47
|
+
return getTypeAliasPageFragment(node, scope);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a Dart scalar enum (all empty variants).
|
|
52
|
+
*/
|
|
53
|
+
function getScalarEnumPageFragment(
|
|
54
|
+
node: DefinedTypeNode,
|
|
55
|
+
scope: RenderScope,
|
|
56
|
+
): Fragment {
|
|
57
|
+
const name = node.name as string;
|
|
58
|
+
const typeName = scope.nameApi.dataType(name);
|
|
59
|
+
const enumNode = node.type;
|
|
60
|
+
if (enumNode.kind !== "enumTypeNode") return emptyFragment();
|
|
61
|
+
|
|
62
|
+
const variants = enumNode.variants.map((v) => {
|
|
63
|
+
const variantName = scope.nameApi.enumVariant(v.name as string);
|
|
64
|
+
return variantName;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const variantLines = variants.map((v: string) => ` ${v},`).join("\n");
|
|
68
|
+
const encoderName = scope.nameApi.encoderFunction(name);
|
|
69
|
+
const decoderName = scope.nameApi.decoderFunction(name);
|
|
70
|
+
const codecName = scope.nameApi.codecFunction(name);
|
|
71
|
+
|
|
72
|
+
// Determine size codec from the enum node's size if available
|
|
73
|
+
const resolvedSize = resolveNestedTypeNode(enumNode.size);
|
|
74
|
+
const sizeType = resolvedSize.format ?? "u8";
|
|
75
|
+
const sizeCodecName = getNumberCodecName(sizeType);
|
|
76
|
+
|
|
77
|
+
return fragment`// Auto-generated. Do not edit.
|
|
78
|
+
// ignore_for_file: type=lint
|
|
79
|
+
|
|
80
|
+
${use("Uint8List", "dartTypedData")}
|
|
81
|
+
${use("Encoder", "solanaCodecsCore")}
|
|
82
|
+
${use("Decoder", "solanaCodecsCore")}
|
|
83
|
+
${use("Codec", "solanaCodecsCore")}
|
|
84
|
+
${use("combineCodec", "solanaCodecsCore")}
|
|
85
|
+
${use("transformEncoder", "solanaCodecsCore")}
|
|
86
|
+
${use("transformDecoder", "solanaCodecsCore")}
|
|
87
|
+
${use(`get${sizeCodecName}Encoder`, "solanaCodecsNumbers")}
|
|
88
|
+
${use(`get${sizeCodecName}Decoder`, "solanaCodecsNumbers")}
|
|
89
|
+
|
|
90
|
+
enum ${fragmentFromString(typeName)} {
|
|
91
|
+
${fragmentFromString(variantLines)}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
95
|
+
return transformEncoder(
|
|
96
|
+
get${fragmentFromString(sizeCodecName)}Encoder(),
|
|
97
|
+
(${fragmentFromString(typeName)} value) => value.index,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
102
|
+
return transformDecoder(
|
|
103
|
+
get${fragmentFromString(sizeCodecName)}Decoder(),
|
|
104
|
+
(int value, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}.values[value],
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
|
|
109
|
+
return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
|
|
110
|
+
}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generate a Dart data enum (sealed class with variants).
|
|
115
|
+
*/
|
|
116
|
+
function getDataEnumPageFragment(
|
|
117
|
+
node: DefinedTypeNode,
|
|
118
|
+
scope: RenderScope,
|
|
119
|
+
): Fragment {
|
|
120
|
+
const name = node.name as string;
|
|
121
|
+
const typeName = scope.nameApi.dataType(name);
|
|
122
|
+
const enumNode = node.type;
|
|
123
|
+
if (enumNode.kind !== "enumTypeNode") return emptyFragment();
|
|
124
|
+
|
|
125
|
+
const variantClasses: string[] = [];
|
|
126
|
+
const encoderVariants: string[] = [];
|
|
127
|
+
const decoderVariants: string[] = [];
|
|
128
|
+
// Collect all field type manifests from variants so we can merge their imports
|
|
129
|
+
const allVariantManifests: { encoder: Fragment; decoder: Fragment; type: Fragment }[] = [];
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < enumNode.variants.length; i++) {
|
|
132
|
+
const variant = enumNode.variants[i];
|
|
133
|
+
const variantClassName = scope.nameApi.sealedClassVariant(
|
|
134
|
+
name,
|
|
135
|
+
variant.name as string,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (variant.kind === "enumEmptyVariantTypeNode") {
|
|
139
|
+
variantClasses.push(`final class ${variantClassName} extends ${typeName} {
|
|
140
|
+
const ${variantClassName}();
|
|
141
|
+
|
|
142
|
+
@override
|
|
143
|
+
bool operator ==(Object other) =>
|
|
144
|
+
identical(this, other) || other is ${variantClassName};
|
|
145
|
+
|
|
146
|
+
@override
|
|
147
|
+
int get hashCode => runtimeType.hashCode;
|
|
148
|
+
|
|
149
|
+
@override
|
|
150
|
+
String toString() => '${typeName}.${variantClassName}()';
|
|
151
|
+
}`);
|
|
152
|
+
|
|
153
|
+
encoderVariants.push(
|
|
154
|
+
`(${i}, getStructEncoder(<(String, Encoder<Object?>)>[]))`,
|
|
155
|
+
);
|
|
156
|
+
decoderVariants.push(
|
|
157
|
+
`(${i}, getStructDecoder(<(String, Decoder<Object?>)>[]).map((_) => const ${variantClassName}()))`,
|
|
158
|
+
);
|
|
159
|
+
} else if (variant.kind === "enumStructVariantTypeNode") {
|
|
160
|
+
const resolvedStruct = resolveNestedTypeNode(variant.struct);
|
|
161
|
+
const fields = resolvedStruct.fields;
|
|
162
|
+
|
|
163
|
+
// Visit each field type once and collect manifests
|
|
164
|
+
const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
|
|
165
|
+
field: f,
|
|
166
|
+
manifest: visit(f.type, scope.typeManifestVisitor),
|
|
167
|
+
}));
|
|
168
|
+
for (const { manifest } of fieldManifests) {
|
|
169
|
+
allVariantManifests.push(manifest);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const fieldDecls = fieldManifests
|
|
173
|
+
.map(({ field: f, manifest }) => {
|
|
174
|
+
return ` final ${manifest.type.content} ${camelCase(f.name as string)};`;
|
|
175
|
+
})
|
|
176
|
+
.join("\n");
|
|
177
|
+
|
|
178
|
+
const ctorParams = fields
|
|
179
|
+
.map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
|
|
180
|
+
.join("\n");
|
|
181
|
+
|
|
182
|
+
const eqChecks = fields
|
|
183
|
+
.map((f: StructFieldTypeNode) => `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`)
|
|
184
|
+
.join(" &&\n ");
|
|
185
|
+
|
|
186
|
+
const hashFields = fields
|
|
187
|
+
.map((f: StructFieldTypeNode) => camelCase(f.name as string))
|
|
188
|
+
.join(", ");
|
|
189
|
+
|
|
190
|
+
const toStringFields = fields
|
|
191
|
+
.map((f: StructFieldTypeNode) => `${camelCase(f.name as string)}: $${camelCase(f.name as string)}`)
|
|
192
|
+
.join(", ");
|
|
193
|
+
|
|
194
|
+
variantClasses.push(`final class ${variantClassName} extends ${typeName} {
|
|
195
|
+
const ${variantClassName}({
|
|
196
|
+
${ctorParams}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
${fieldDecls}
|
|
200
|
+
|
|
201
|
+
@override
|
|
202
|
+
bool operator ==(Object other) =>
|
|
203
|
+
identical(this, other) ||
|
|
204
|
+
other is ${variantClassName} &&
|
|
205
|
+
${eqChecks};
|
|
206
|
+
|
|
207
|
+
@override
|
|
208
|
+
int get hashCode => Object.hash(${hashFields});
|
|
209
|
+
|
|
210
|
+
@override
|
|
211
|
+
String toString() => '${typeName}.${variantClassName}(${toStringFields})';
|
|
212
|
+
}`);
|
|
213
|
+
|
|
214
|
+
const encFields = fieldManifests
|
|
215
|
+
.map(({ field: f, manifest }) => {
|
|
216
|
+
return `('${f.name as string}', ${manifest.encoder.content})`;
|
|
217
|
+
})
|
|
218
|
+
.join(", ");
|
|
219
|
+
|
|
220
|
+
const decFields = fieldManifests
|
|
221
|
+
.map(({ field: f, manifest }) => {
|
|
222
|
+
return `('${f.name as string}', ${manifest.decoder.content})`;
|
|
223
|
+
})
|
|
224
|
+
.join(", ");
|
|
225
|
+
|
|
226
|
+
const fromMapFields = fieldManifests
|
|
227
|
+
.map(
|
|
228
|
+
({ field: f, manifest }) => {
|
|
229
|
+
const typeStr = manifest.type.content;
|
|
230
|
+
const isNullable = typeStr.endsWith("?");
|
|
231
|
+
const accessor = isNullable ? `map['${f.name as string}']` : `map['${f.name as string}']!`;
|
|
232
|
+
return `${camelCase(f.name as string)}: ${accessor} as ${typeStr}`;
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
.join(", ");
|
|
236
|
+
|
|
237
|
+
const toMapFields = fields
|
|
238
|
+
.map(
|
|
239
|
+
(f: StructFieldTypeNode) =>
|
|
240
|
+
`'${f.name as string}': value.${camelCase(f.name as string)}`,
|
|
241
|
+
)
|
|
242
|
+
.join(", ");
|
|
243
|
+
|
|
244
|
+
encoderVariants.push(
|
|
245
|
+
`(${i}, transformEncoder(getStructEncoder([${encFields}]), (${variantClassName} value) => <String, Object?>{${toMapFields}}))`,
|
|
246
|
+
);
|
|
247
|
+
decoderVariants.push(
|
|
248
|
+
`(${i}, transformDecoder(getStructDecoder([${decFields}]), (Map<String, Object?> map, Uint8List bytes, int offset) => ${variantClassName}(${fromMapFields})))`,
|
|
249
|
+
);
|
|
250
|
+
} else if (variant.kind === "enumTupleVariantTypeNode") {
|
|
251
|
+
const resolvedTuple = resolveNestedTypeNode(variant.tuple);
|
|
252
|
+
const items = resolvedTuple.items;
|
|
253
|
+
if (items.length === 1) {
|
|
254
|
+
const manifest = visit(items[0], scope.typeManifestVisitor);
|
|
255
|
+
allVariantManifests.push(manifest);
|
|
256
|
+
variantClasses.push(`final class ${variantClassName} extends ${typeName} {
|
|
257
|
+
const ${variantClassName}(this.value);
|
|
258
|
+
|
|
259
|
+
final ${manifest.type.content} value;
|
|
260
|
+
|
|
261
|
+
@override
|
|
262
|
+
bool operator ==(Object other) =>
|
|
263
|
+
identical(this, other) ||
|
|
264
|
+
other is ${variantClassName} && value == other.value;
|
|
265
|
+
|
|
266
|
+
@override
|
|
267
|
+
int get hashCode => value.hashCode;
|
|
268
|
+
|
|
269
|
+
@override
|
|
270
|
+
String toString() => '${typeName}.${variantClassName}(\$value)';
|
|
271
|
+
}`);
|
|
272
|
+
encoderVariants.push(
|
|
273
|
+
`(${i}, transformEncoder(${manifest.encoder.content}, (${variantClassName} value) => value.value))`,
|
|
274
|
+
);
|
|
275
|
+
decoderVariants.push(
|
|
276
|
+
`(${i}, transformDecoder(${manifest.decoder.content}, (${manifest.type.content} value, Uint8List bytes, int offset) => ${variantClassName}(value)))`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const encoderName = scope.nameApi.encoderFunction(name);
|
|
283
|
+
const decoderName = scope.nameApi.decoderFunction(name);
|
|
284
|
+
const codecName = scope.nameApi.codecFunction(name);
|
|
285
|
+
const resolvedSize = resolveNestedTypeNode(enumNode.size);
|
|
286
|
+
const sizeType = resolvedSize.format ?? "u8";
|
|
287
|
+
const sizeCodecName = getNumberCodecName(sizeType);
|
|
288
|
+
|
|
289
|
+
const result = fragment`// Auto-generated. Do not edit.
|
|
290
|
+
// ignore_for_file: type=lint
|
|
291
|
+
|
|
292
|
+
${use("Uint8List", "dartTypedData")}
|
|
293
|
+
${use("immutable", "meta")}
|
|
294
|
+
${use("Encoder", "solanaCodecsCore")}
|
|
295
|
+
${use("Decoder", "solanaCodecsCore")}
|
|
296
|
+
${use("Codec", "solanaCodecsCore")}
|
|
297
|
+
${use("combineCodec", "solanaCodecsCore")}
|
|
298
|
+
${use("transformEncoder", "solanaCodecsCore")}
|
|
299
|
+
${use("transformDecoder", "solanaCodecsCore")}
|
|
300
|
+
${use("getStructEncoder", "solanaCodecsDataStructures")}
|
|
301
|
+
${use("getStructDecoder", "solanaCodecsDataStructures")}
|
|
302
|
+
${use("getDiscriminatedUnionEncoder", "solanaCodecsDataStructures")}
|
|
303
|
+
${use("getDiscriminatedUnionDecoder", "solanaCodecsDataStructures")}
|
|
304
|
+
${use(`get${sizeCodecName}Encoder`, "solanaCodecsNumbers")}
|
|
305
|
+
${use(`get${sizeCodecName}Decoder`, "solanaCodecsNumbers")}
|
|
306
|
+
|
|
307
|
+
sealed class ${fragmentFromString(typeName)} {
|
|
308
|
+
const ${fragmentFromString(typeName)}();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
${fragmentFromString(variantClasses.join("\n\n"))}
|
|
312
|
+
|
|
313
|
+
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
314
|
+
return getDiscriminatedUnionEncoder([
|
|
315
|
+
${fragmentFromString(encoderVariants.join(",\n "))},
|
|
316
|
+
], size: get${fragmentFromString(sizeCodecName)}Encoder());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
320
|
+
return getDiscriminatedUnionDecoder([
|
|
321
|
+
${fragmentFromString(decoderVariants.join(",\n "))},
|
|
322
|
+
], size: get${fragmentFromString(sizeCodecName)}Decoder());
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
|
|
326
|
+
return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
|
|
327
|
+
}`;
|
|
328
|
+
|
|
329
|
+
// Merge variant field type manifest imports into the result
|
|
330
|
+
for (const manifest of allVariantManifests) {
|
|
331
|
+
result.imports.mergeWith(manifest.encoder.imports);
|
|
332
|
+
result.imports.mergeWith(manifest.decoder.imports);
|
|
333
|
+
result.imports.mergeWith(manifest.type.imports);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Generate a Dart @immutable class for a struct type.
|
|
341
|
+
*/
|
|
342
|
+
function getStructPageFragment(
|
|
343
|
+
node: DefinedTypeNode,
|
|
344
|
+
scope: RenderScope,
|
|
345
|
+
): Fragment {
|
|
346
|
+
const name = node.name as string;
|
|
347
|
+
const typeName = scope.nameApi.dataType(name);
|
|
348
|
+
const structNode = node.type;
|
|
349
|
+
if (structNode.kind !== "structTypeNode") return emptyFragment();
|
|
350
|
+
|
|
351
|
+
const fields = structNode.fields;
|
|
352
|
+
|
|
353
|
+
// Visit each field type once and collect manifests for reuse
|
|
354
|
+
const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
|
|
355
|
+
field: f,
|
|
356
|
+
manifest: visit(f.type, scope.typeManifestVisitor),
|
|
357
|
+
}));
|
|
358
|
+
|
|
359
|
+
const fieldDecls = fieldManifests
|
|
360
|
+
.map(({ field: f, manifest }) => {
|
|
361
|
+
return ` final ${manifest.type.content} ${camelCase(f.name as string)};`;
|
|
362
|
+
})
|
|
363
|
+
.join("\n");
|
|
364
|
+
|
|
365
|
+
const ctorParams = fields
|
|
366
|
+
.map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
|
|
367
|
+
.join("\n");
|
|
368
|
+
|
|
369
|
+
const eqChecks =
|
|
370
|
+
fields.length === 0
|
|
371
|
+
? "true"
|
|
372
|
+
: fields
|
|
373
|
+
.map(
|
|
374
|
+
(f: StructFieldTypeNode) =>
|
|
375
|
+
`${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`,
|
|
376
|
+
)
|
|
377
|
+
.join(" &&\n ");
|
|
378
|
+
|
|
379
|
+
const hashFields = fields
|
|
380
|
+
.map((f: StructFieldTypeNode) => camelCase(f.name as string))
|
|
381
|
+
.join(", ");
|
|
382
|
+
|
|
383
|
+
const toStringFields = fields
|
|
384
|
+
.map(
|
|
385
|
+
(f: StructFieldTypeNode) =>
|
|
386
|
+
`${camelCase(f.name as string)}: \$${camelCase(f.name as string)}`,
|
|
387
|
+
)
|
|
388
|
+
.join(", ");
|
|
389
|
+
|
|
390
|
+
const encoderName = scope.nameApi.encoderFunction(name);
|
|
391
|
+
const decoderName = scope.nameApi.decoderFunction(name);
|
|
392
|
+
const codecName = scope.nameApi.codecFunction(name);
|
|
393
|
+
|
|
394
|
+
// Build encoder/decoder field lists
|
|
395
|
+
const encFields = fieldManifests
|
|
396
|
+
.map(({ field: f, manifest }) => {
|
|
397
|
+
return ` ('${f.name as string}', ${manifest.encoder.content}),`;
|
|
398
|
+
})
|
|
399
|
+
.join("\n");
|
|
400
|
+
|
|
401
|
+
const decFields = fieldManifests
|
|
402
|
+
.map(({ field: f, manifest }) => {
|
|
403
|
+
return ` ('${f.name as string}', ${manifest.decoder.content}),`;
|
|
404
|
+
})
|
|
405
|
+
.join("\n");
|
|
406
|
+
|
|
407
|
+
const toMapFields = fields
|
|
408
|
+
.map(
|
|
409
|
+
(f: StructFieldTypeNode) =>
|
|
410
|
+
` '${f.name as string}': value.${camelCase(f.name as string)},`,
|
|
411
|
+
)
|
|
412
|
+
.join("\n");
|
|
413
|
+
|
|
414
|
+
const fromMapFields = fieldManifests
|
|
415
|
+
.map(({ field: f, manifest }) => {
|
|
416
|
+
const typeStr = manifest.type.content;
|
|
417
|
+
const isNullable = typeStr.endsWith("?");
|
|
418
|
+
const accessor = isNullable ? `map['${f.name as string}']` : `map['${f.name as string}']!`;
|
|
419
|
+
return ` ${camelCase(f.name as string)}: ${accessor} as ${typeStr},`;
|
|
420
|
+
})
|
|
421
|
+
.join("\n");
|
|
422
|
+
|
|
423
|
+
const result = fragment`// Auto-generated. Do not edit.
|
|
424
|
+
// ignore_for_file: type=lint
|
|
425
|
+
|
|
426
|
+
${use("Uint8List", "dartTypedData")}
|
|
427
|
+
${use("immutable", "meta")}
|
|
428
|
+
${use("Encoder", "solanaCodecsCore")}
|
|
429
|
+
${use("Decoder", "solanaCodecsCore")}
|
|
430
|
+
${use("Codec", "solanaCodecsCore")}
|
|
431
|
+
${use("combineCodec", "solanaCodecsCore")}
|
|
432
|
+
${use("transformEncoder", "solanaCodecsCore")}
|
|
433
|
+
${use("transformDecoder", "solanaCodecsCore")}
|
|
434
|
+
${use("getStructEncoder", "solanaCodecsDataStructures")}
|
|
435
|
+
${use("getStructDecoder", "solanaCodecsDataStructures")}
|
|
436
|
+
|
|
437
|
+
@immutable
|
|
438
|
+
class ${fragmentFromString(typeName)} {
|
|
439
|
+
const ${fragmentFromString(typeName)}({
|
|
440
|
+
${fragmentFromString(ctorParams)}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
${fragmentFromString(fieldDecls)}
|
|
444
|
+
|
|
445
|
+
@override
|
|
446
|
+
bool operator ==(Object other) =>
|
|
447
|
+
identical(this, other) ||
|
|
448
|
+
other is ${fragmentFromString(typeName)} &&
|
|
449
|
+
runtimeType == other.runtimeType &&
|
|
450
|
+
${fragmentFromString(eqChecks)};
|
|
451
|
+
|
|
452
|
+
@override
|
|
453
|
+
int get hashCode => Object.hash(${fragmentFromString(hashFields)});
|
|
454
|
+
|
|
455
|
+
@override
|
|
456
|
+
String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
460
|
+
final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
|
|
461
|
+
${fragmentFromString(encFields)}
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
return transformEncoder(
|
|
465
|
+
structEncoder,
|
|
466
|
+
(${fragmentFromString(typeName)} value) => <String, Object?>{
|
|
467
|
+
${fragmentFromString(toMapFields)}
|
|
468
|
+
},
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
473
|
+
final structDecoder = getStructDecoder(<(String, Decoder<Object?>)>[
|
|
474
|
+
${fragmentFromString(decFields)}
|
|
475
|
+
]);
|
|
476
|
+
|
|
477
|
+
return transformDecoder(
|
|
478
|
+
structDecoder,
|
|
479
|
+
(Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}(
|
|
480
|
+
${fragmentFromString(fromMapFields)}
|
|
481
|
+
),
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
|
|
486
|
+
return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
|
|
487
|
+
}`;
|
|
488
|
+
|
|
489
|
+
// Merge field type manifest imports (encoder, decoder, type) into the result
|
|
490
|
+
for (const { manifest } of fieldManifests) {
|
|
491
|
+
result.imports.mergeWith(manifest.encoder.imports);
|
|
492
|
+
result.imports.mergeWith(manifest.decoder.imports);
|
|
493
|
+
result.imports.mergeWith(manifest.type.imports);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Generate a simple type alias with codec wrappers.
|
|
501
|
+
*/
|
|
502
|
+
function getTypeAliasPageFragment(
|
|
503
|
+
node: DefinedTypeNode,
|
|
504
|
+
scope: RenderScope,
|
|
505
|
+
): Fragment {
|
|
506
|
+
const name = node.name as string;
|
|
507
|
+
const typeName = scope.nameApi.dataType(name);
|
|
508
|
+
const manifest = visit(node.type, scope.typeManifestVisitor);
|
|
509
|
+
|
|
510
|
+
const encoderName = scope.nameApi.encoderFunction(name);
|
|
511
|
+
const decoderName = scope.nameApi.decoderFunction(name);
|
|
512
|
+
const codecName = scope.nameApi.codecFunction(name);
|
|
513
|
+
|
|
514
|
+
return fragment`// Auto-generated. Do not edit.
|
|
515
|
+
// ignore_for_file: type=lint
|
|
516
|
+
|
|
517
|
+
${use("Encoder", "solanaCodecsCore")}
|
|
518
|
+
${use("Decoder", "solanaCodecsCore")}
|
|
519
|
+
${use("Codec", "solanaCodecsCore")}
|
|
520
|
+
${use("combineCodec", "solanaCodecsCore")}
|
|
521
|
+
|
|
522
|
+
typedef ${fragmentFromString(typeName)} = ${manifest.type};
|
|
523
|
+
|
|
524
|
+
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
525
|
+
return ${manifest.encoder};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
529
|
+
return ${manifest.decoder};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
|
|
533
|
+
return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
|
|
534
|
+
}`;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function getNumberCodecName(format: string): string {
|
|
538
|
+
switch (format) {
|
|
539
|
+
case "u8": return "U8";
|
|
540
|
+
case "u16": return "U16";
|
|
541
|
+
case "u32": return "U32";
|
|
542
|
+
case "u64": return "U64";
|
|
543
|
+
case "u128": return "U128";
|
|
544
|
+
case "i8": return "I8";
|
|
545
|
+
case "i16": return "I16";
|
|
546
|
+
case "i32": return "I32";
|
|
547
|
+
case "i64": return "I64";
|
|
548
|
+
case "i128": return "I128";
|
|
549
|
+
case "f32": return "F32";
|
|
550
|
+
case "f64": return "F64";
|
|
551
|
+
case "shortU16": return "ShortU16";
|
|
552
|
+
default: return "U8";
|
|
553
|
+
}
|
|
554
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { renderVisitor } from "./visitors/renderVisitor.js";
|
|
2
|
+
export { getRenderMapVisitor } from "./visitors/getRenderMapVisitor.js";
|
|
3
|
+
export { getTypeManifestVisitor } from "./visitors/getTypeManifestVisitor.js";
|
|
4
|
+
export { DartImportMap, DART_EXTERNAL_PACKAGE_MAP } from "./utils/importMap.js";
|
|
5
|
+
export { createDartNameApi } from "./utils/nameTransformers.js";
|
|
6
|
+
export type { DartNameApi } from "./utils/nameTransformers.js";
|
|
7
|
+
export type { RenderOptions, GetRenderMapOptions, RenderScope } from "./utils/options.js";
|
|
8
|
+
export type { TypeManifest } from "./utils/typeManifest.js";
|
|
9
|
+
export type { Fragment } from "./utils/fragment.js";
|
|
10
|
+
|
|
11
|
+
// Default export for Codama CLI integration
|
|
12
|
+
import { renderVisitor } from "./visitors/renderVisitor.js";
|
|
13
|
+
export default renderVisitor;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a Uint8Array to a Dart hex literal string.
|
|
3
|
+
* Used for discriminator constants.
|
|
4
|
+
*/
|
|
5
|
+
export function bytesToDartHexList(bytes: Uint8Array): string {
|
|
6
|
+
const pairs: string[] = [];
|
|
7
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
8
|
+
pairs.push(`0x${bytes[i].toString(16).padStart(2, "0")}`);
|
|
9
|
+
}
|
|
10
|
+
return `[${pairs.join(", ")}]`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert a Uint8Array to a Dart Uint8List.fromList() expression.
|
|
15
|
+
*/
|
|
16
|
+
export function bytesToDartUint8List(bytes: Uint8Array): string {
|
|
17
|
+
const hex = bytesToDartHexList(bytes);
|
|
18
|
+
return `Uint8List.fromList(${hex})`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format Dart code using `dart format`.
|
|
5
|
+
* Falls back to returning the unformatted code if dart is not available.
|
|
6
|
+
*/
|
|
7
|
+
export function formatDartCode(code: string): string {
|
|
8
|
+
try {
|
|
9
|
+
const result = execSync("dart format --fix -", {
|
|
10
|
+
input: code,
|
|
11
|
+
encoding: "utf-8",
|
|
12
|
+
timeout: 30_000,
|
|
13
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
14
|
+
});
|
|
15
|
+
return result;
|
|
16
|
+
} catch {
|
|
17
|
+
// If dart format is not available, return as-is
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format all Dart files in a directory using `dart format`.
|
|
24
|
+
*/
|
|
25
|
+
export function formatDartDirectory(dir: string): void {
|
|
26
|
+
try {
|
|
27
|
+
execSync(`dart format --fix "${dir}"`, {
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
timeout: 60_000,
|
|
30
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
// Silently ignore if dart format is not available
|
|
34
|
+
}
|
|
35
|
+
}
|