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.
Files changed (45) hide show
  1. package/dist/index.browser.cjs +456 -127
  2. package/dist/index.browser.cjs.map +1 -1
  3. package/dist/index.browser.js +456 -128
  4. package/dist/index.browser.js.map +1 -1
  5. package/dist/index.node.cjs +456 -127
  6. package/dist/index.node.cjs.map +1 -1
  7. package/dist/index.node.js +456 -128
  8. package/dist/index.node.js.map +1 -1
  9. package/dist/index.react-native.js +456 -128
  10. package/dist/index.react-native.js.map +1 -1
  11. package/dist/types/fragments/accountPage.d.ts.map +1 -1
  12. package/dist/types/fragments/instructionPage.d.ts.map +1 -1
  13. package/dist/types/fragments/typePage.d.ts.map +1 -1
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/utils/discriminators.d.ts +10 -0
  17. package/dist/types/utils/discriminators.d.ts.map +1 -0
  18. package/dist/types/utils/exactDecoder.d.ts +17 -0
  19. package/dist/types/utils/exactDecoder.d.ts.map +1 -0
  20. package/dist/types/utils/formatCode.d.ts +1 -1
  21. package/dist/types/utils/formatCode.d.ts.map +1 -1
  22. package/dist/types/utils/index.d.ts +4 -0
  23. package/dist/types/utils/index.d.ts.map +1 -1
  24. package/dist/types/utils/normalizeRootNode.d.ts +11 -0
  25. package/dist/types/utils/normalizeRootNode.d.ts.map +1 -0
  26. package/dist/types/utils/valueNodes.d.ts +16 -0
  27. package/dist/types/utils/valueNodes.d.ts.map +1 -0
  28. package/dist/types/visitors/getTypeManifestVisitor.d.ts +1 -1
  29. package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
  30. package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
  31. package/package.json +21 -18
  32. package/readme.md +29 -0
  33. package/src/fragments/accountPage.ts +90 -17
  34. package/src/fragments/instructionPage.ts +103 -77
  35. package/src/fragments/typePage.ts +32 -12
  36. package/src/index.ts +1 -0
  37. package/src/utils/discriminators.ts +121 -0
  38. package/src/utils/exactDecoder.ts +82 -0
  39. package/src/utils/formatCode.ts +7 -11
  40. package/src/utils/index.ts +4 -0
  41. package/src/utils/normalizeRootNode.ts +76 -0
  42. package/src/utils/valueNodes.ts +87 -0
  43. package/src/visitors/getRenderMapVisitor.ts +1 -1
  44. package/src/visitors/getTypeManifestVisitor.ts +14 -8
  45. 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
- )}(${itemManifest.encoder}${encoderSizeExpr})`,
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 fields = node.fields.map((f) => visit(f, self));
331
- const fieldNames = node.fields.map((f) => f.name as string);
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
- root,
42
+ normalizedRoot,
40
43
  getRenderMapVisitor({ nameApi, dependencyMap }),
41
44
  );
42
45