codama-renderers-dart 0.5.0 → 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/dist/index.browser.cjs +456 -127
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +456 -128
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +456 -127
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +456 -128
- package/dist/index.node.js.map +1 -1
- package/dist/index.react-native.js +456 -128
- 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/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/index.d.ts +4 -0
- package/dist/types/utils/index.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 +21 -18
- package/readme.md +29 -0
- package/src/fragments/accountPage.ts +90 -17
- package/src/fragments/instructionPage.ts +103 -77
- package/src/fragments/typePage.ts +32 -12
- 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/index.ts +4 -0
- package/src/utils/normalizeRootNode.ts +76 -0
- package/src/utils/valueNodes.ts +87 -0
- package/src/visitors/getRenderMapVisitor.ts +1 -1
- package/src/visitors/getTypeManifestVisitor.ts +14 -8
- package/src/visitors/renderVisitor.ts +4 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { RootNode } from "@codama/nodes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a serialized Codama root before it reaches Codama's visitors.
|
|
5
|
+
*
|
|
6
|
+
* Codama constructors materialize empty child collections, while generated
|
|
7
|
+
* JSON commonly omits them. Pina intentionally omits those empty fields, so
|
|
8
|
+
* restore only the structural defaults required by Codama traversal. This is
|
|
9
|
+
* a copy: callers retain their original parsed IDL unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeRootNode(root: RootNode): RootNode {
|
|
12
|
+
return normalizeNode(root) as RootNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeNode(value: unknown): unknown {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map(normalizeNode);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!isRecord(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const normalized = Object.fromEntries(
|
|
25
|
+
Object.entries(value).map(([key, child]) => [key, normalizeNode(child)]),
|
|
26
|
+
) as Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
switch (normalized.kind) {
|
|
29
|
+
case "rootNode":
|
|
30
|
+
setEmptyCollection(normalized, "additionalPrograms");
|
|
31
|
+
break;
|
|
32
|
+
case "programNode":
|
|
33
|
+
setEmptyCollection(normalized, "accounts");
|
|
34
|
+
setEmptyCollection(normalized, "constants");
|
|
35
|
+
setEmptyCollection(normalized, "definedTypes");
|
|
36
|
+
setEmptyCollection(normalized, "errors");
|
|
37
|
+
setEmptyCollection(normalized, "events");
|
|
38
|
+
setEmptyCollection(normalized, "instructions");
|
|
39
|
+
setEmptyCollection(normalized, "pdas");
|
|
40
|
+
break;
|
|
41
|
+
case "instructionNode":
|
|
42
|
+
setEmptyCollection(normalized, "accounts");
|
|
43
|
+
setEmptyCollection(normalized, "arguments");
|
|
44
|
+
break;
|
|
45
|
+
case "pdaNode":
|
|
46
|
+
setEmptyCollection(normalized, "seeds");
|
|
47
|
+
break;
|
|
48
|
+
case "structTypeNode":
|
|
49
|
+
setEmptyCollection(normalized, "fields");
|
|
50
|
+
break;
|
|
51
|
+
case "enumTypeNode":
|
|
52
|
+
setEmptyCollection(normalized, "variants");
|
|
53
|
+
break;
|
|
54
|
+
case "tupleTypeNode":
|
|
55
|
+
setEmptyCollection(normalized, "items");
|
|
56
|
+
break;
|
|
57
|
+
case "hiddenPrefixTypeNode":
|
|
58
|
+
setEmptyCollection(normalized, "prefix");
|
|
59
|
+
break;
|
|
60
|
+
case "hiddenSuffixTypeNode":
|
|
61
|
+
setEmptyCollection(normalized, "suffix");
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
69
|
+
return typeof value === "object" && value !== null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setEmptyCollection(record: Record<string, unknown>, key: string): void {
|
|
73
|
+
if (record[key] === undefined) {
|
|
74
|
+
record[key] = [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InstructionInputValueNode,
|
|
3
|
+
ValueNode,
|
|
4
|
+
} from "@codama/nodes";
|
|
5
|
+
|
|
6
|
+
import type { Fragment } from "./fragment.js";
|
|
7
|
+
import { fragment, fragmentFromString, use } from "./fragment.js";
|
|
8
|
+
import { WELL_KNOWN_ADDRESSES } from "./wellKnownAddresses.js";
|
|
9
|
+
|
|
10
|
+
type RenderableValueNode = InstructionInputValueNode | ValueNode;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render a standalone Codama value as a Dart expression.
|
|
14
|
+
*
|
|
15
|
+
* Contextual instruction values need resolver state and are deliberately
|
|
16
|
+
* rejected here. Defaults and discriminators must be deterministic at code
|
|
17
|
+
* generation time so generated encoders cannot silently choose a different
|
|
18
|
+
* wire value.
|
|
19
|
+
*/
|
|
20
|
+
export function getDartValueFragment(
|
|
21
|
+
value: RenderableValueNode,
|
|
22
|
+
dartType?: string,
|
|
23
|
+
): Fragment {
|
|
24
|
+
switch (value.kind) {
|
|
25
|
+
case "numberValueNode":
|
|
26
|
+
return fragmentFromString(
|
|
27
|
+
dartType === "BigInt"
|
|
28
|
+
? `BigInt.from(${value.number})`
|
|
29
|
+
: String(value.number),
|
|
30
|
+
);
|
|
31
|
+
case "booleanValueNode":
|
|
32
|
+
return fragmentFromString(String(value.boolean));
|
|
33
|
+
case "stringValueNode":
|
|
34
|
+
return fragmentFromString(toDartStringLiteral(value.string));
|
|
35
|
+
case "noneValueNode":
|
|
36
|
+
return fragmentFromString("null");
|
|
37
|
+
case "bytesValueNode": {
|
|
38
|
+
if (value.encoding !== "base16") {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unsupported deterministic bytes encoding: ${value.encoding}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const clean = value.data.replace(/^0x/, "");
|
|
44
|
+
if (clean.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(clean)) {
|
|
45
|
+
throw new Error(`Invalid hexadecimal bytes value: ${value.data}`);
|
|
46
|
+
}
|
|
47
|
+
const bytes = clean.match(/.{2}/g)?.map((byte) => `0x${byte}`) ?? [];
|
|
48
|
+
return fragment`${use("Uint8List", "dartTypedData")}.fromList([${fragmentFromString(bytes.join(", "))}])`;
|
|
49
|
+
}
|
|
50
|
+
case "publicKeyValueNode": {
|
|
51
|
+
const wellKnownName = WELL_KNOWN_ADDRESSES.get(value.publicKey);
|
|
52
|
+
return wellKnownName
|
|
53
|
+
? fragment`${use(wellKnownName, "solanaAddresses")}`
|
|
54
|
+
: fragment`${use("Address", "solanaAddresses")}(${fragmentFromString(toDartStringLiteral(value.publicKey))})`;
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unsupported deterministic Dart value node: ${value.kind}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Return whether a rendered standalone value is valid in a const initializer. */
|
|
64
|
+
export function isConstDartValueNode(value: RenderableValueNode): boolean {
|
|
65
|
+
switch (value.kind) {
|
|
66
|
+
case "numberValueNode":
|
|
67
|
+
case "booleanValueNode":
|
|
68
|
+
case "stringValueNode":
|
|
69
|
+
case "noneValueNode":
|
|
70
|
+
case "publicKeyValueNode":
|
|
71
|
+
return true;
|
|
72
|
+
default:
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function toDartStringLiteral(value: string): string {
|
|
78
|
+
return `'${value
|
|
79
|
+
.replaceAll("\\", "\\\\")
|
|
80
|
+
.replaceAll("\n", "\\n")
|
|
81
|
+
.replaceAll("\r", "\\r")
|
|
82
|
+
.replaceAll("\t", "\\t")
|
|
83
|
+
.replaceAll("\b", "\\b")
|
|
84
|
+
.replaceAll("\f", "\\f")
|
|
85
|
+
.replaceAll("$", "\\$")
|
|
86
|
+
.replaceAll("'", "\\'")}'`;
|
|
87
|
+
}
|
|
@@ -86,7 +86,7 @@ export function getRenderMapVisitor(
|
|
|
86
86
|
visitRoot(node: RootNode, { self }) {
|
|
87
87
|
const programMaps = [
|
|
88
88
|
visit(node.program, self),
|
|
89
|
-
...node.additionalPrograms.map((p) => visit(p, self)),
|
|
89
|
+
...(node.additionalPrograms ?? []).map((p) => visit(p, self)),
|
|
90
90
|
];
|
|
91
91
|
|
|
92
92
|
const mergedProgramMap = mergeRenderMaps(programMaps);
|
|
@@ -228,13 +228,18 @@ export function getTypeManifestVisitor(input: {
|
|
|
228
228
|
const itemManifest = visit(node.item, self);
|
|
229
229
|
const encoderSizeExpr = getArraySizeExpression(node, "Encoder");
|
|
230
230
|
const decoderSizeExpr = getArraySizeExpression(node, "Decoder");
|
|
231
|
+
const itemType = fragmentFromString(itemManifest.type.content);
|
|
232
|
+
const itemEncoder = fragment`${use(
|
|
233
|
+
"transformEncoder",
|
|
234
|
+
"solanaCodecsCore",
|
|
235
|
+
)}(${itemManifest.encoder}, (${itemType} value) => value)`;
|
|
231
236
|
|
|
232
237
|
return {
|
|
233
238
|
type: fragment`List<${itemManifest.type}>`,
|
|
234
239
|
encoder: fragment`${use(
|
|
235
240
|
"getArrayEncoder",
|
|
236
241
|
"solanaCodecsDataStructures",
|
|
237
|
-
)}(${
|
|
242
|
+
)}<${itemType}>(${itemEncoder}${encoderSizeExpr})`,
|
|
238
243
|
decoder: fragment`${use(
|
|
239
244
|
"getArrayDecoder",
|
|
240
245
|
"solanaCodecsDataStructures",
|
|
@@ -286,7 +291,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
286
291
|
},
|
|
287
292
|
|
|
288
293
|
visitTupleType(node: TupleTypeNode, { self }) {
|
|
289
|
-
const itemManifests = node.items.map((item) => visit(item, self));
|
|
294
|
+
const itemManifests = (node.items ?? []).map((item) => visit(item, self));
|
|
290
295
|
const typeList = itemManifests
|
|
291
296
|
.map((m) => m.type.content)
|
|
292
297
|
.join(", ");
|
|
@@ -327,8 +332,9 @@ export function getTypeManifestVisitor(input: {
|
|
|
327
332
|
},
|
|
328
333
|
|
|
329
334
|
visitStructType(node: StructTypeNode, { self }) {
|
|
330
|
-
const
|
|
331
|
-
const
|
|
335
|
+
const nodeFields = node.fields ?? [];
|
|
336
|
+
const fields = nodeFields.map((f) => visit(f, self));
|
|
337
|
+
const fieldNames = nodeFields.map((f) => f.name as string);
|
|
332
338
|
|
|
333
339
|
// Type: Map<String, Object?> (for inline structs without a name)
|
|
334
340
|
const typeStr = "Map<String, Object?>";
|
|
@@ -371,7 +377,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
371
377
|
|
|
372
378
|
visitEnumType(node: EnumTypeNode, { self }) {
|
|
373
379
|
// Check if this is a scalar enum (all empty variants)
|
|
374
|
-
const isScalar = node.variants.every(
|
|
380
|
+
const isScalar = (node.variants ?? []).every(
|
|
375
381
|
(v) => v.kind === "enumEmptyVariantTypeNode",
|
|
376
382
|
);
|
|
377
383
|
|
|
@@ -498,7 +504,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
498
504
|
encoder: fragment`${use(
|
|
499
505
|
"fixEncoderSize",
|
|
500
506
|
"solanaCodecsCore",
|
|
501
|
-
)}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))})`,
|
|
507
|
+
)}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))}, allowTruncation: false)`,
|
|
502
508
|
decoder: fragment`${use(
|
|
503
509
|
"fixDecoderSize",
|
|
504
510
|
"solanaCodecsCore",
|
|
@@ -549,7 +555,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
549
555
|
|
|
550
556
|
visitHiddenPrefixType(node: HiddenPrefixTypeNode, { self }) {
|
|
551
557
|
const innerManifest = visit(node.type, self);
|
|
552
|
-
const prefixes = node.prefix.map((p) => getHiddenAffixManifest(p, self));
|
|
558
|
+
const prefixes = (node.prefix ?? []).map((p) => getHiddenAffixManifest(p, self));
|
|
553
559
|
const prefixEncoders = prefixes
|
|
554
560
|
.map((p) => p.encoder.content)
|
|
555
561
|
.join(", ");
|
|
@@ -584,7 +590,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
584
590
|
|
|
585
591
|
visitHiddenSuffixType(node: HiddenSuffixTypeNode, { self }) {
|
|
586
592
|
const innerManifest = visit(node.type, self);
|
|
587
|
-
const suffixes = node.suffix.map((s) => getHiddenAffixManifest(s, self));
|
|
593
|
+
const suffixes = (node.suffix ?? []).map((s) => getHiddenAffixManifest(s, self));
|
|
588
594
|
const suffixEncoders = suffixes
|
|
589
595
|
.map((s) => s.encoder.content)
|
|
590
596
|
.join(", ");
|
|
@@ -9,6 +9,7 @@ import type { Fragment } from "../utils/fragment.js";
|
|
|
9
9
|
import type { RenderOptions } from "../utils/options.js";
|
|
10
10
|
import { DartImportMap, DART_EXTERNAL_PACKAGE_MAP } from "../utils/importMap.js";
|
|
11
11
|
import { formatDartDirectory } from "../utils/formatCode.js";
|
|
12
|
+
import { normalizeRootNode } from "../utils/normalizeRootNode.js";
|
|
12
13
|
import { getRenderMapVisitor } from "./getRenderMapVisitor.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -29,6 +30,8 @@ export function renderVisitor(
|
|
|
29
30
|
} = options;
|
|
30
31
|
|
|
31
32
|
return rootNodeVisitor((root: RootNode) => {
|
|
33
|
+
const normalizedRoot = normalizeRootNode(root);
|
|
34
|
+
|
|
32
35
|
// 1. Optionally delete the output directory
|
|
33
36
|
if (deleteFolderBeforeRendering && existsSync(outputDir)) {
|
|
34
37
|
deleteDirectory(outputDir);
|
|
@@ -36,7 +39,7 @@ export function renderVisitor(
|
|
|
36
39
|
|
|
37
40
|
// 2. Build the render map
|
|
38
41
|
const renderMap = visit(
|
|
39
|
-
|
|
42
|
+
normalizedRoot,
|
|
40
43
|
getRenderMapVisitor({ nameApi, dependencyMap }),
|
|
41
44
|
);
|
|
42
45
|
|