codama-renderers-dart 0.5.1 → 0.5.4

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 (37) hide show
  1. package/{readme.md → README.md} +16 -43
  2. package/dist/index.browser.cjs +345 -124
  3. package/dist/index.browser.cjs.map +1 -1
  4. package/dist/index.browser.js +346 -125
  5. package/dist/index.browser.js.map +1 -1
  6. package/dist/index.node.cjs +345 -124
  7. package/dist/index.node.cjs.map +1 -1
  8. package/dist/index.node.js +346 -125
  9. package/dist/index.node.js.map +1 -1
  10. package/dist/index.react-native.js +346 -125
  11. package/dist/index.react-native.js.map +1 -1
  12. package/dist/types/fragments/accountPage.d.ts.map +1 -1
  13. package/dist/types/fragments/errorPage.d.ts.map +1 -1
  14. package/dist/types/fragments/instructionPage.d.ts.map +1 -1
  15. package/dist/types/fragments/pdaPage.d.ts.map +1 -1
  16. package/dist/types/fragments/programPage.d.ts.map +1 -1
  17. package/dist/types/utils/nameTransformers.d.ts +5 -0
  18. package/dist/types/utils/nameTransformers.d.ts.map +1 -1
  19. package/dist/types/utils/valueNodes.d.ts +4 -0
  20. package/dist/types/utils/valueNodes.d.ts.map +1 -1
  21. package/dist/types/visitors/getRenderMapVisitor.d.ts.map +1 -1
  22. package/dist/types/visitors/getTypeManifestVisitor.d.ts +1 -1
  23. package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
  24. package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
  25. package/package.json +7 -7
  26. package/src/fragments/accountPage.ts +2 -1
  27. package/src/fragments/errorPage.ts +8 -5
  28. package/src/fragments/instructionPage.ts +54 -17
  29. package/src/fragments/pdaPage.ts +7 -14
  30. package/src/fragments/programPage.ts +3 -2
  31. package/src/fragments/typePage.ts +102 -19
  32. package/src/utils/formatCode.ts +2 -2
  33. package/src/utils/nameTransformers.ts +39 -1
  34. package/src/utils/valueNodes.ts +24 -1
  35. package/src/visitors/getRenderMapVisitor.ts +30 -5
  36. package/src/visitors/getTypeManifestVisitor.ts +123 -81
  37. package/src/visitors/renderVisitor.ts +11 -13
@@ -2,13 +2,13 @@ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join, dirname, posix } from 'path';
3
3
  import { staticVisitor, extendVisitor, visit, NodeStack, LinkableDictionary, pipe, recordNodeStackVisitor, recordLinkablesOnFirstVisitVisitor, rootNodeVisitor } from '@codama/visitors-core';
4
4
  import { createRenderMap, addToRenderMap, mergeRenderMaps, deleteDirectory } from '@codama/renderers-core';
5
- import { execSync } from 'child_process';
5
+ import { execFileSync } from 'child_process';
6
6
  import { resolveNestedTypeNode, getAllInstructionsWithSubs } from '@codama/nodes';
7
7
  import { CodamaError, CODAMA_ERROR__UNEXPECTED_NODE_KIND } from '@codama/errors';
8
8
 
9
9
  // src/visitors/renderVisitor.ts
10
10
  function formatDartDirectory(dir) {
11
- execSync(`dart format "${dir}"`, {
11
+ execFileSync("dart", ["format", "--", dir], {
12
12
  encoding: "utf-8",
13
13
  timeout: 6e4,
14
14
  stdio: ["pipe", "pipe", "pipe"]
@@ -78,13 +78,89 @@ function setEmptyCollection(record, key) {
78
78
 
79
79
  // src/utils/nameTransformers.ts
80
80
  function pascalCase(str) {
81
+ assertSafeName(str);
81
82
  return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
82
83
  }
84
+ var DART_RESERVED_WORDS = /* @__PURE__ */ new Set([
85
+ "abstract",
86
+ "as",
87
+ "assert",
88
+ "async",
89
+ "augment",
90
+ "await",
91
+ "base",
92
+ "break",
93
+ "case",
94
+ "catch",
95
+ "class",
96
+ "const",
97
+ "continue",
98
+ "covariant",
99
+ "default",
100
+ "deferred",
101
+ "do",
102
+ "dynamic",
103
+ "else",
104
+ "enum",
105
+ "export",
106
+ "extends",
107
+ "extension",
108
+ "external",
109
+ "factory",
110
+ "false",
111
+ "final",
112
+ "finally",
113
+ "for",
114
+ "get",
115
+ "hide",
116
+ "if",
117
+ "implements",
118
+ "import",
119
+ "in",
120
+ "interface",
121
+ "is",
122
+ "late",
123
+ "library",
124
+ "mixin",
125
+ "native",
126
+ "new",
127
+ "null",
128
+ "of",
129
+ "on",
130
+ "operator",
131
+ "part",
132
+ "required",
133
+ "rethrow",
134
+ "return",
135
+ "sealed",
136
+ "set",
137
+ "show",
138
+ "static",
139
+ "super",
140
+ "switch",
141
+ "sync",
142
+ "this",
143
+ "throw",
144
+ "true",
145
+ "try",
146
+ "typedef",
147
+ "var",
148
+ "void",
149
+ "when",
150
+ "while",
151
+ "with",
152
+ "yield"
153
+ ]);
154
+ function escapeDartIdentifier(name) {
155
+ return DART_RESERVED_WORDS.has(name) ? `${name}_` : name;
156
+ }
83
157
  function camelCase(str) {
84
158
  const pascal = pascalCase(str);
85
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
159
+ const cased = pascal.charAt(0).toLowerCase() + pascal.slice(1);
160
+ return escapeDartIdentifier(cased);
86
161
  }
87
162
  function snakeCase(str) {
163
+ assertSafeName(str);
88
164
  return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").replace(/_+/g, "_").toLowerCase();
89
165
  }
90
166
  function createDartNameApi() {
@@ -110,6 +186,11 @@ function createDartNameApi() {
110
186
  discriminatorConstant: (name) => `${camelCase(name)}Discriminator`
111
187
  };
112
188
  }
189
+ function assertSafeName(name) {
190
+ if (!/^[a-zA-Z0-9_ -]*$/.test(name)) {
191
+ throw new Error(`Invalid Dart node name: ${JSON.stringify(name)}`);
192
+ }
193
+ }
113
194
 
114
195
  // src/utils/codecs.ts
115
196
  function bytesToDartHexList(bytes) {
@@ -320,10 +401,19 @@ var WELL_KNOWN_ADDRESSES = /* @__PURE__ */ new Map([
320
401
  function getDartValueFragment(value, dartType) {
321
402
  switch (value.kind) {
322
403
  case "numberValueNode":
404
+ if (typeof value.number !== "number" || !Number.isFinite(value.number)) {
405
+ throw new Error("Invalid Dart number: expected a finite number");
406
+ }
407
+ if (dartType === "BigInt" && !Number.isSafeInteger(value.number)) {
408
+ throw new Error("Invalid Dart BigInt: expected a safe integer");
409
+ }
323
410
  return fragmentFromString(
324
411
  dartType === "BigInt" ? `BigInt.from(${value.number})` : String(value.number)
325
412
  );
326
413
  case "booleanValueNode":
414
+ if (typeof value.boolean !== "boolean") {
415
+ throw new Error("Invalid Dart boolean: expected a boolean");
416
+ }
327
417
  return fragmentFromString(String(value.boolean));
328
418
  case "stringValueNode":
329
419
  return fragmentFromString(toDartStringLiteral(value.string));
@@ -367,6 +457,12 @@ function isConstDartValueNode(value) {
367
457
  function toDartStringLiteral(value) {
368
458
  return `'${value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t").replaceAll("\b", "\\b").replaceAll("\f", "\\f").replaceAll("$", "\\$").replaceAll("'", "\\'")}'`;
369
459
  }
460
+ function getNonNegativeInteger(value) {
461
+ if (!Number.isSafeInteger(value) || value < 0) {
462
+ throw new Error("Expected a non-negative safe integer value");
463
+ }
464
+ return String(value);
465
+ }
370
466
 
371
467
  // src/utils/discriminators.ts
372
468
  function getDiscriminatorValidationFragment(node, scope) {
@@ -567,23 +663,12 @@ function getTypeManifestVisitor(input) {
567
663
  }
568
664
  );
569
665
  return extendVisitor(baseVisitor, {
570
- visitNumberType(node, { self }) {
571
- const { format, endian } = node;
572
- const endianSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
573
- const endianDecoderSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
574
- const dartType = getNumberDartType(format);
575
- const codecName = getNumberCodecName(format);
666
+ visitNumberType(node) {
576
667
  return {
577
668
  // `BigInt` and other number types live in `dart:core` and need no import.
578
- type: fragmentFromString(dartType),
579
- encoder: fragment`${use(
580
- `get${codecName}Encoder`,
581
- "solanaCodecsNumbers"
582
- )}(${endianSuffix ? fragmentFromString(endianSuffix.slice(2)) : fragmentFromString("")})`,
583
- decoder: fragment`${use(
584
- `get${codecName}Decoder`,
585
- "solanaCodecsNumbers"
586
- )}(${endianDecoderSuffix ? fragmentFromString(endianDecoderSuffix.slice(2)) : fragmentFromString("")})`,
669
+ type: fragmentFromString(getNumberDartType(node.format)),
670
+ encoder: getNumberCodecExpression(node, "Encoder"),
671
+ decoder: getNumberCodecExpression(node, "Decoder"),
587
672
  value: emptyTypeManifest().value,
588
673
  isEnum: false
589
674
  };
@@ -670,7 +755,7 @@ function getTypeManifestVisitor(input) {
670
755
  encoder: fragment`${use(
671
756
  "getArrayEncoder",
672
757
  "solanaCodecsDataStructures"
673
- )}<${itemType}>(${itemEncoder}${encoderSizeExpr})`,
758
+ )}(${itemEncoder}${encoderSizeExpr})`,
674
759
  decoder: fragment`${use(
675
760
  "getArrayDecoder",
676
761
  "solanaCodecsDataStructures"
@@ -726,18 +811,28 @@ function getTypeManifestVisitor(input) {
726
811
  typeFragment.imports.mergeWith(m.type.imports);
727
812
  }
728
813
  const encoderListStr = encoderList.map((e) => e.content).join(", ");
814
+ const decoderListStr = decoderList.map((d) => d.content).join(", ");
815
+ let tupleEncoderFn = "getTupleEncoder";
816
+ let tupleDecoderFn = "getTupleDecoder";
817
+ let tupleEncoderArgs = `[${encoderListStr}]`;
818
+ let tupleDecoderArgs = `[${decoderListStr}]`;
819
+ if (itemManifests.length === 2) {
820
+ tupleEncoderFn = "getTuple2Encoder";
821
+ tupleDecoderFn = "getTuple2Decoder";
822
+ tupleEncoderArgs = encoderListStr;
823
+ tupleDecoderArgs = decoderListStr;
824
+ }
729
825
  const encoderFrag = fragment`${use(
730
- "getTupleEncoder",
826
+ tupleEncoderFn,
731
827
  "solanaCodecsDataStructures"
732
- )}([${fragmentFromString(encoderListStr)}])`;
828
+ )}(${tupleEncoderArgs})`;
733
829
  for (const e of encoderList) {
734
830
  encoderFrag.imports.mergeWith(e.imports);
735
831
  }
736
- const decoderListStr = decoderList.map((d) => d.content).join(", ");
737
832
  const decoderFrag = fragment`${use(
738
- "getTupleDecoder",
833
+ tupleDecoderFn,
739
834
  "solanaCodecsDataStructures"
740
- )}([${fragmentFromString(decoderListStr)}])`;
835
+ )}(${tupleDecoderArgs})`;
741
836
  for (const d of decoderList) {
742
837
  decoderFrag.imports.mergeWith(d.imports);
743
838
  }
@@ -754,7 +849,7 @@ function getTypeManifestVisitor(input) {
754
849
  const fields = nodeFields.map((f) => visit(f, self));
755
850
  const fieldNames = nodeFields.map((f) => f.name);
756
851
  const typeStr = "Map<String, Object?>";
757
- const encoderFields = fields.map((f, i) => `('${fieldNames[i]}', ${f.encoder.content})`).join(", ");
852
+ const encoderFields = fields.map((f, i) => `(${toDartStringLiteral(fieldNames[i])}, ${f.encoder.content})`).join(", ");
758
853
  const encoderFrag = fragment`${use(
759
854
  "getStructEncoder",
760
855
  "solanaCodecsDataStructures"
@@ -762,7 +857,7 @@ function getTypeManifestVisitor(input) {
762
857
  for (const f of fields) {
763
858
  encoderFrag.imports.mergeWith(f.encoder.imports);
764
859
  }
765
- const decoderFields = fields.map((f, i) => `('${fieldNames[i]}', ${f.decoder.content})`).join(", ");
860
+ const decoderFields = fields.map((f, i) => `(${toDartStringLiteral(fieldNames[i])}, ${f.decoder.content})`).join(", ");
766
861
  const decoderFrag = fragment`${use(
767
862
  "getStructDecoder",
768
863
  "solanaCodecsDataStructures"
@@ -816,19 +911,22 @@ function getTypeManifestVisitor(input) {
816
911
  const itemManifest = visit(node.item, self);
817
912
  const itemTypeStr = itemManifest.type.content;
818
913
  const resolvedPrefix = resolveNestedTypeNode(node.prefix);
819
- const prefixExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Encoder")}` : "";
820
- const prefixDecoderExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Decoder")}` : "";
914
+ const prefixExpr = resolvedPrefix.format !== "u8" ? fragment`, prefix: ${getNumberCodecExpression(resolvedPrefix, "Encoder")}` : "";
915
+ const prefixDecoderExpr = resolvedPrefix.format !== "u8" ? fragment`, prefix: ${getNumberCodecExpression(resolvedPrefix, "Decoder")}` : "";
821
916
  const noneValueExpr = node.fixed ? ", noneValue: const ZeroesNoneValue()" : "";
822
917
  return {
823
918
  type: fragment`${itemManifest.type}?`,
824
919
  encoder: fragment`${use(
825
920
  "getNullableEncoder",
826
921
  "solanaCodecsDataStructures"
827
- )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.encoder}${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
922
+ )}<${fragmentFromString(itemTypeStr)}>(${use(
923
+ "transformEncoder",
924
+ "solanaCodecsCore"
925
+ )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${prefixExpr}${fragmentFromString(noneValueExpr)})`,
828
926
  decoder: fragment`${use(
829
927
  "getNullableDecoder",
830
928
  "solanaCodecsDataStructures"
831
- )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${fragmentFromString(prefixDecoderExpr)}${fragmentFromString(noneValueExpr)})`,
929
+ )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${prefixDecoderExpr}${fragmentFromString(noneValueExpr)})`,
832
930
  value: emptyTypeManifest().value,
833
931
  isEnum: false
834
932
  };
@@ -874,11 +972,11 @@ function getTypeManifestVisitor(input) {
874
972
  encoder: fragment`${use(
875
973
  "fixEncoderSize",
876
974
  "solanaCodecsCore"
877
- )}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))}, allowTruncation: false)`,
975
+ )}(${innerManifest.encoder}, ${fragmentFromString(getNonNegativeInteger(node.size))}, allowTruncation: false)`,
878
976
  decoder: fragment`${use(
879
977
  "fixDecoderSize",
880
978
  "solanaCodecsCore"
881
- )}(${innerManifest.decoder}, ${fragmentFromString(String(node.size))})`,
979
+ )}(${innerManifest.decoder}, ${fragmentFromString(getNonNegativeInteger(node.size))})`,
882
980
  value: innerManifest.value,
883
981
  isEnum: innerManifest.isEnum
884
982
  };
@@ -888,10 +986,10 @@ function getTypeManifestVisitor(input) {
888
986
  const bigIntFormats = /* @__PURE__ */ new Set(["u64", "u128", "i64", "i128"]);
889
987
  let prefixManifest;
890
988
  if (node.prefix.kind === "numberTypeNode" && bigIntFormats.has(node.prefix.format)) {
891
- const codecName = getNumberCodecName(node.prefix.format);
989
+ const numberManifest = visit(node.prefix, self);
892
990
  prefixManifest = {
893
- encoder: fragment`transformEncoder(${use(`get${codecName}Encoder`, "solanaCodecsNumbers")}(), (size) => BigInt.from(size))`,
894
- decoder: fragment`transformDecoder(${use(`get${codecName}Decoder`, "solanaCodecsNumbers")}(), (size, _, __) => size.toInt())`
991
+ encoder: fragment`${use("transformEncoder", "solanaCodecsCore")}(${numberManifest.encoder}, (size) => BigInt.from(size))`,
992
+ decoder: fragment`${use("transformDecoder", "solanaCodecsCore")}(${numberManifest.decoder}, (size, _, __) => size.toInt())`
895
993
  };
896
994
  } else {
897
995
  prefixManifest = visit(node.prefix, self);
@@ -982,10 +1080,10 @@ function getTypeManifestVisitor(input) {
982
1080
  };
983
1081
  },
984
1082
  visitPreOffsetType(node, { self }) {
985
- return visit(node.type, self);
1083
+ return getOffsetManifest(node, self);
986
1084
  },
987
1085
  visitPostOffsetType(node, { self }) {
988
- return visit(node.type, self);
1086
+ return getOffsetManifest(node, self);
989
1087
  },
990
1088
  visitSolAmountType(node, { self }) {
991
1089
  return visit(node.number, self);
@@ -1074,16 +1172,17 @@ function getNumberCodecName(format) {
1074
1172
  return "U8";
1075
1173
  }
1076
1174
  }
1077
- function getNumberCodecExpression(format, codecType) {
1078
- const name = getNumberCodecName(format);
1079
- return `get${name}${codecType}()`;
1175
+ function getNumberCodecExpression(node, codecType) {
1176
+ const name = getNumberCodecName(node.format);
1177
+ const config = node.endian === "be" ? fragment`${use("NumberCodecConfig", "solanaCodecsNumbers")}(endian: ${use("Endian", "dartTypedData")}.big)` : "";
1178
+ return fragment`${use(`get${name}${codecType}`, "solanaCodecsNumbers")}(${config})`;
1080
1179
  }
1081
1180
  function getArraySizeExpression(node, codecType) {
1082
1181
  if (!("count" in node) || !node.count) return "";
1083
1182
  const count = node.count;
1084
1183
  switch (count.kind) {
1085
1184
  case "fixedCountNode":
1086
- return `, size: FixedArraySize(${count.value})`;
1185
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1087
1186
  case "remainderCountNode":
1088
1187
  return ", size: RemainderArraySize()";
1089
1188
  case "prefixedCountNode": {
@@ -1091,7 +1190,7 @@ function getArraySizeExpression(node, codecType) {
1091
1190
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1092
1191
  return "";
1093
1192
  }
1094
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1193
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1095
1194
  }
1096
1195
  default:
1097
1196
  return "";
@@ -1102,7 +1201,7 @@ function getMapSizeExpression(node, codecType) {
1102
1201
  const count = node.count;
1103
1202
  switch (count.kind) {
1104
1203
  case "fixedCountNode":
1105
- return `, size: FixedArraySize(${count.value})`;
1204
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1106
1205
  case "remainderCountNode":
1107
1206
  return ", size: RemainderArraySize()";
1108
1207
  case "prefixedCountNode": {
@@ -1110,7 +1209,7 @@ function getMapSizeExpression(node, codecType) {
1110
1209
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1111
1210
  return "";
1112
1211
  }
1113
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1212
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1114
1213
  }
1115
1214
  default:
1116
1215
  return "";
@@ -1121,7 +1220,7 @@ function getSetSizeExpression(node, codecType) {
1121
1220
  const count = node.count;
1122
1221
  switch (count.kind) {
1123
1222
  case "fixedCountNode":
1124
- return `, size: FixedArraySize(${count.value})`;
1223
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1125
1224
  case "remainderCountNode":
1126
1225
  return ", size: RemainderArraySize()";
1127
1226
  case "prefixedCountNode": {
@@ -1129,7 +1228,7 @@ function getSetSizeExpression(node, codecType) {
1129
1228
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1130
1229
  return "";
1131
1230
  }
1132
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1231
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1133
1232
  }
1134
1233
  default:
1135
1234
  return "";
@@ -1140,36 +1239,61 @@ function getHiddenAffixManifest(node, self) {
1140
1239
  const manifest = visit(node, self);
1141
1240
  return { encoder: manifest.encoder, decoder: manifest.decoder };
1142
1241
  }
1143
- const constantBytes = getConstantBytesExpression(node);
1242
+ const constantBytes = getConstantBytesExpression(node, self);
1144
1243
  return {
1145
1244
  encoder: fragment`${use("getConstantEncoder", "solanaCodecsDataStructures")}(${constantBytes})`,
1146
1245
  decoder: fragment`${use("getConstantDecoder", "solanaCodecsDataStructures")}(${constantBytes})`
1147
1246
  };
1148
1247
  }
1149
- function getConstantBytesExpression(node) {
1150
- const valueNode = node.value;
1151
- const typeNode = node.type;
1152
- if (valueNode?.kind !== "numberValueNode") {
1248
+ function getConstantBytesExpression(node, self) {
1249
+ if (node.value.kind !== "numberValueNode") {
1153
1250
  throw new Error(
1154
- `Unsupported hidden affix constant value kind: ${valueNode?.kind}`
1251
+ `Unsupported hidden affix constant value kind: ${node.value.kind}`
1155
1252
  );
1156
1253
  }
1157
- const value = Number(valueNode.number);
1158
- if (typeNode?.kind === "numberTypeNode") {
1159
- return fragment`${use(`get${getNumberCodecName(typeNode.format)}Encoder`, "solanaCodecsNumbers")}().encode(${value})`;
1254
+ const manifest = visit(node.type, self);
1255
+ if (!["int", "BigInt", "double"].includes(manifest.type.content)) {
1256
+ throw new Error(`Unsupported hidden affix constant type kind: ${node.type.kind}`);
1160
1257
  }
1161
- if (typeNode?.kind === "preOffsetTypeNode") {
1162
- const inner = typeNode.type;
1163
- if (inner?.kind !== "numberTypeNode") {
1164
- throw new Error(
1165
- `Unsupported hidden affix pre-offset inner type kind: ${inner?.kind}`
1166
- );
1167
- }
1168
- return fragment`${use("padLeftEncoder", "solanaCodecsCore")}(${use(`get${getNumberCodecName(inner.format)}Encoder`, "solanaCodecsNumbers")}(), ${typeNode.offset}).encode(${value})`;
1258
+ const value = getDartValueFragment(node.value, manifest.type.content);
1259
+ return fragment`${manifest.encoder}.encode(${value})`;
1260
+ }
1261
+ function getOffsetManifest(node, self) {
1262
+ if (!Number.isSafeInteger(node.offset)) {
1263
+ throw new Error("Expected a safe integer for a codec offset");
1169
1264
  }
1170
- throw new Error(
1171
- `Unsupported hidden affix constant type kind: ${typeNode?.kind}`
1172
- );
1265
+ const manifest = visit(node.type, self);
1266
+ const pre = node.kind === "preOffsetTypeNode";
1267
+ const key = pre ? "preOffset" : "postOffset";
1268
+ if (node.strategy === "padded") {
1269
+ const side = pre ? "Left" : "Right";
1270
+ const offset = getNonNegativeInteger(node.offset);
1271
+ return {
1272
+ ...manifest,
1273
+ encoder: fragment`${use(`pad${side}Encoder`, "solanaCodecsCore")}(${manifest.encoder}, ${offset})`,
1274
+ decoder: fragment`${use(`pad${side}Decoder`, "solanaCodecsCore")}(${manifest.decoder}, ${offset})`
1275
+ };
1276
+ }
1277
+ let expression;
1278
+ switch (node.strategy) {
1279
+ case "absolute":
1280
+ expression = node.offset < 0 ? `scope.wrapBytes(${node.offset})` : String(node.offset);
1281
+ break;
1282
+ case "relative":
1283
+ expression = `scope.${key} + ${node.offset}`;
1284
+ break;
1285
+ case "preOffset":
1286
+ expression = `scope.preOffset + ${node.offset}`;
1287
+ break;
1288
+ default:
1289
+ throw new Error(`Unsupported offset strategy: ${node.strategy}`);
1290
+ }
1291
+ const config = fragment`${use("OffsetConfig", "solanaCodecsCore")}(${key}: (scope) => ${expression})`;
1292
+ return {
1293
+ ...manifest,
1294
+ encoder: fragment`${use("offsetEncoder", "solanaCodecsCore")}(${manifest.encoder}, ${config})`,
1295
+ decoder: fragment`${use("offsetDecoder", "solanaCodecsCore")}(${manifest.decoder}, ${config})`
1296
+ };
1173
1297
  }
1174
1298
 
1175
1299
  // src/fragments/discriminatorConstants.ts
@@ -1302,7 +1426,7 @@ ${ctorParams}
1302
1426
  const decodeFnName = scope.nameApi.accountDecodeFunction(name);
1303
1427
  const sizeFragment = node.size != null ? fragment`
1304
1428
  /// The size of the [${fragmentFromString(typeName)}] account data in bytes.
1305
- const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(String(node.size))};` : emptyFragment();
1429
+ const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(getNonNegativeInteger(node.size))};` : emptyFragment();
1306
1430
  const discFragment = getDiscriminatorConstantsFragment(node, scope);
1307
1431
  const discriminatorValidation = getDiscriminatorValidationFragment(
1308
1432
  node,
@@ -1416,9 +1540,10 @@ function getErrorPageFragment(programNode, scope) {
1416
1540
  programName,
1417
1541
  err.name
1418
1542
  );
1543
+ getNonNegativeInteger(err.code);
1419
1544
  const hexCode = `0x${err.code.toString(16)}`;
1420
- const docs = err.docs?.length ? err.docs.map((d) => `/// ${d}`).join("\n") + "\n" : "";
1421
- const message = err.message ? `/// Message: "${err.message}"
1545
+ const docs = err.docs?.length ? err.docs.map(toDartDocComment).join("\n") + "\n" : "";
1546
+ const message = err.message ? `${toDartDocComment(`Message: "${err.message}"`)}
1422
1547
  ` : "";
1423
1548
  return `${docs}${message}const int ${constName} = ${hexCode}; // ${err.code}`;
1424
1549
  }).join("\n\n");
@@ -1428,7 +1553,7 @@ function getErrorPageFragment(programNode, scope) {
1428
1553
  err.name
1429
1554
  );
1430
1555
  const message = err.message ?? err.name;
1431
- return ` ${constName}: '${escapeString(message)}',`;
1556
+ return ` ${constName}: ${toDartStringLiteral(message)},`;
1432
1557
  }).join("\n");
1433
1558
  const errorMessageFnName = scope.nameApi.errorMessageFunction(programName);
1434
1559
  const isErrorFnName = `is${pascalCase(programName)}Error`;
@@ -1454,8 +1579,8 @@ bool ${fragmentFromString(isErrorFnName)}(int code) {
1454
1579
  return _${fragmentFromString(camelCase(programName))}ErrorMessages.containsKey(code);
1455
1580
  }`;
1456
1581
  }
1457
- function escapeString(str) {
1458
- return str.replace(/'/g, "\\'").replace(/\n/g, "\\n");
1582
+ function toDartDocComment(value) {
1583
+ return value.split(/\r\n?|\n/).map((line) => `/// ${line}`).join("\n");
1459
1584
  }
1460
1585
 
1461
1586
  // src/fragments/indexPage.ts
@@ -1581,10 +1706,28 @@ ${dataCtorInitializers}` : ""};`;
1581
1706
  const dataEncoderName = `get${typeName}InstructionDataEncoder`;
1582
1707
  const dataDecoderName = `get${typeName}InstructionDataDecoder`;
1583
1708
  const dataCodecName = `get${typeName}InstructionDataCodec`;
1584
- const argNames = new Set(args.map((a) => camelCase(a.name)));
1585
- const accountNames = new Set(accounts.map((a) => camelCase(a.name)));
1586
- const hasProgramAddressCollision = argNames.has("programAddress") || accountNames.has("programAddress");
1587
- const instrProgramParam = hasProgramAddressCollision ? "instructionProgramAddress" : "programAddress";
1709
+ const usedNames = /* @__PURE__ */ new Set([
1710
+ ...args.map((arg) => camelCase(arg.name)),
1711
+ ...accounts.map((account) => camelCase(account.name))
1712
+ ]);
1713
+ const instrProgramParam = reserveName(
1714
+ usedNames.has("programAddress") ? "instructionProgramAddress" : "programAddress",
1715
+ usedNames
1716
+ );
1717
+ const instructionDataLocal = reserveName("instructionData", usedNames);
1718
+ const signerParams = /* @__PURE__ */ new Map();
1719
+ for (const account of accounts) {
1720
+ if (account.isSigner === "either") {
1721
+ signerParams.set(
1722
+ account,
1723
+ reserveName(`${camelCase(account.name)}IsSigner`, usedNames)
1724
+ );
1725
+ }
1726
+ }
1727
+ const signerParamDeclarations = [...signerParams.values()].map((parameter) => ` bool ${parameter} = true,`).join("\n");
1728
+ const signerParamDocs = [...signerParams.entries()].map(
1729
+ ([account, parameter]) => `/// Set [${parameter}] to false when [${camelCase(account.name)}] does not sign (for example, a multisig authority).`
1730
+ ).join("\n");
1588
1731
  const accountParams = accounts.map((acc) => {
1589
1732
  const fieldName = camelCase(acc.name);
1590
1733
  const isRequired = !(acc.isOptional ?? false);
@@ -1604,7 +1747,7 @@ ${dataCtorInitializers}` : ""};`;
1604
1747
  }).join("\n");
1605
1748
  const accountMetas = accounts.map((acc) => {
1606
1749
  const fieldName = camelCase(acc.name);
1607
- const role = getAccountRole(acc);
1750
+ const role = getAccountRole(acc, signerParams.get(acc));
1608
1751
  const isOptional = acc.isOptional ?? false;
1609
1752
  if (isOptional) {
1610
1753
  if (optionalAccountStrategy === "omitted") {
@@ -1687,13 +1830,15 @@ Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}
1687
1830
  return combineCodec(${fragmentFromString(dataEncoderName)}(), ${fragmentFromString(dataDecoderName)}());
1688
1831
  }`);
1689
1832
  parts.push(fragment`
1690
- /// Creates a [${fragmentFromString(typeName)}] instruction.
1833
+ /// Creates a [${fragmentFromString(typeName)}] instruction.${fragmentFromString(signerParamDocs ? `
1834
+ ${signerParamDocs}` : "")}
1691
1835
  Instruction ${fragmentFromString(instrFnName)}({
1692
1836
  required Address ${fragmentFromString(instrProgramParam)},
1693
1837
  ${fragmentFromString(accountParams)}
1694
- ${fragmentFromString(argParams)}
1838
+ ${fragmentFromString(argParams)}${fragmentFromString(signerParamDeclarations ? `
1839
+ ${signerParamDeclarations}` : "")}
1695
1840
  }) {
1696
- final instructionData = ${fragmentFromString(dataClassName)}(
1841
+ final ${instructionDataLocal} = ${fragmentFromString(dataClassName)}(
1697
1842
  ${fragmentFromString(dataConstruction)}
1698
1843
  );
1699
1844
 
@@ -1702,7 +1847,7 @@ ${fragmentFromString(dataConstruction)}
1702
1847
  accounts: [
1703
1848
  ${fragmentFromString(accountMetas)}
1704
1849
  ],
1705
- data: ${fragmentFromString(dataEncoderName)}().encode(instructionData),
1850
+ data: ${fragmentFromString(dataEncoderName)}().encode(${instructionDataLocal}),
1706
1851
  );
1707
1852
  }`);
1708
1853
  parts.push(fragment`
@@ -1721,13 +1866,20 @@ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruct
1721
1866
  }
1722
1867
  return result;
1723
1868
  }
1724
- function getAccountRole(acc) {
1725
- const isSigner = acc.isSigner === true || acc.isSigner === "either";
1726
- const isWritable = acc.isWritable ?? false;
1727
- if (isSigner && isWritable) return "AccountRole.writableSigner";
1728
- if (isSigner) return "AccountRole.readonlySigner";
1729
- if (isWritable) return "AccountRole.writable";
1730
- return "AccountRole.readonly";
1869
+ function reserveName(preferredName, usedNames) {
1870
+ let name = preferredName;
1871
+ while (usedNames.has(name)) {
1872
+ name = `${name}_`;
1873
+ }
1874
+ usedNames.add(name);
1875
+ return name;
1876
+ }
1877
+ function getAccountRole(acc, signerParameter) {
1878
+ const role = acc.isWritable ? "AccountRole.writable" : "AccountRole.readonly";
1879
+ if (signerParameter) {
1880
+ return `${signerParameter} ? ${role}Signer : ${role}`;
1881
+ }
1882
+ return acc.isSigner === true ? `${role}Signer` : role;
1731
1883
  }
1732
1884
  function isDiscriminatorArg(arg, node) {
1733
1885
  const discriminators = node.discriminators ?? [];
@@ -1780,9 +1932,9 @@ function getPdaPageFragment(node, scope) {
1780
1932
  for (const seed of seeds) {
1781
1933
  if (seed.kind === "constantPdaSeedNode") {
1782
1934
  if (seed.value.kind === "bytesValueNode") {
1783
- seedValues.push(` Uint8List.fromList(${bytesValueToDart(seed.value.data)}),`);
1935
+ seedValues.push(` ${getDartValueFragment(seed.value).content},`);
1784
1936
  } else if (seed.value.kind === "stringValueNode") {
1785
- seedValues.push(` '${seed.value.string}',`);
1937
+ seedValues.push(` ${toDartStringLiteral(seed.value.string)},`);
1786
1938
  } else if (seed.value.kind === "publicKeyValueNode") {
1787
1939
  const wellKnownSeedName = WELL_KNOWN_ADDRESSES.get(seed.value.publicKey);
1788
1940
  if (wellKnownSeedName) {
@@ -1792,13 +1944,14 @@ function getPdaPageFragment(node, scope) {
1792
1944
  );
1793
1945
  } else {
1794
1946
  seedValues.push(
1795
- ` getAddressEncoder().encode(Address('${seed.value.publicKey}')),`
1947
+ ` getAddressEncoder().encode(Address(${toDartStringLiteral(seed.value.publicKey)})),`
1796
1948
  );
1797
1949
  }
1798
1950
  } else if (seed.value.kind === "numberValueNode") {
1799
1951
  const manifest = visit(seed.type, scope.typeManifestVisitor);
1800
1952
  seedManifests.push(manifest);
1801
- seedValues.push(` ${manifest.encoder.content}.encode(${seed.value.number}),`);
1953
+ const value = getDartValueFragment(seed.value, manifest.type.content);
1954
+ seedValues.push(` ${manifest.encoder.content}.encode(${value.content}),`);
1802
1955
  }
1803
1956
  } else if (seed.kind === "variablePdaSeedNode") {
1804
1957
  const manifest = visit(seed.type, scope.typeManifestVisitor);
@@ -1812,7 +1965,7 @@ function getPdaPageFragment(node, scope) {
1812
1965
  if (wellKnownProgramName) {
1813
1966
  use(wellKnownProgramName, "solanaAddresses");
1814
1967
  }
1815
- const programIdParam = node.programId ? wellKnownProgramName ? `Address programAddress = ${wellKnownProgramName}` : `Address programAddress = const Address('${node.programId}')` : "required Address programAddress";
1968
+ const programIdParam = node.programId ? wellKnownProgramName ? `Address programAddress = ${wellKnownProgramName}` : `Address programAddress = const Address(${toDartStringLiteral(node.programId)})` : "required Address programAddress";
1816
1969
  const typedDataImport = hasByteSeeds ? use("Uint8List", "dartTypedData") : fragmentFromString("");
1817
1970
  const metaImport = hasSeedsClass ? use("immutable", "meta") : fragmentFromString("");
1818
1971
  const parts = [
@@ -1859,14 +2012,6 @@ ${fragmentFromString(seedValues.join("\n"))}
1859
2012
  }
1860
2013
  return result;
1861
2014
  }
1862
- function bytesValueToDart(hex) {
1863
- const clean = hex.replace(/^0x/, "");
1864
- const pairs = [];
1865
- for (let i = 0; i < clean.length; i += 2) {
1866
- pairs.push(`0x${clean.slice(i, i + 2)}`);
1867
- }
1868
- return `[${pairs.join(", ")}]`;
1869
- }
1870
2015
  function getProgramPageFragment(node, scope) {
1871
2016
  const name = node.name;
1872
2017
  const addressConstName = scope.nameApi.programAddressConstant(name);
@@ -1887,7 +2032,7 @@ const ${fragmentFromString(addressConstName)} = ${fragmentFromString(wellKnownNa
1887
2032
  ${use("Address", "solanaAddresses")}
1888
2033
 
1889
2034
  /// The address of the ${fragmentFromString(pascalCase(name))} program.
1890
- const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}');`;
2035
+ const ${fragmentFromString(addressConstName)} = Address(${fragmentFromString(toDartStringLiteral(node.publicKey))});`;
1891
2036
  const parts = [addressDeclaration];
1892
2037
  const accounts = node.accounts ?? [];
1893
2038
  if (accounts.length > 0) {
@@ -2028,7 +2173,7 @@ function getValueFragment(value, dartType) {
2028
2173
  case "booleanValueNode":
2029
2174
  return fragmentFromString(String(value.boolean));
2030
2175
  case "stringValueNode":
2031
- return fragmentFromString(`'${value.string.replaceAll("'", "\\'")}'`);
2176
+ return fragmentFromString(toDartStringLiteral(value.string));
2032
2177
  case "bytesValueNode": {
2033
2178
  const clean = value.data.replace(/^0x/, "");
2034
2179
  const bytes = clean.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [];
@@ -2126,6 +2271,7 @@ function getDataEnumPageFragment(node, variants, scope) {
2126
2271
  const decoderVariants = [];
2127
2272
  const encodeCases = [];
2128
2273
  const decodeCases = [];
2274
+ let pageHasListEnums = false;
2129
2275
  const allVariantManifests = [];
2130
2276
  for (let i = 0; i < variants.length; i++) {
2131
2277
  const variant = variants[i];
@@ -2173,10 +2319,14 @@ function getDataEnumPageFragment(node, variants, scope) {
2173
2319
  const ctorSignature = fields.length === 0 ? ` const ${variantClassName}();` : ` const ${variantClassName}({
2174
2320
  ${ctorParams}
2175
2321
  });`;
2176
- const eqChecks = fields.length === 0 ? "true" : fields.map((f) => `${camelCase(f.name)} == other.${camelCase(f.name)}`).join(" &&\n ");
2177
- const hashExpression = getHashExpression(
2178
- fields.map((f) => camelCase(f.name))
2179
- );
2322
+ const eqChecks = fields.length === 0 ? "true" : fields.map((f, index) => {
2323
+ const fieldName = camelCase(f.name);
2324
+ return isListManifest(fieldManifests[index].manifest) ? `_listEquals(${fieldName}, other.${fieldName})` : `${fieldName} == other.${fieldName}`;
2325
+ }).join(" &&\n ");
2326
+ const hashExpression = getValueHashExpression(fields, fieldManifests);
2327
+ if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
2328
+ pageHasListEnums = true;
2329
+ }
2180
2330
  const toStringFields = fields.map((f) => `${camelCase(f.name)}: $${camelCase(f.name)}`).join(", ");
2181
2331
  variantClasses.push(`final class ${variantClassName} extends ${typeName} {
2182
2332
  ${ctorSignature}
@@ -2282,7 +2432,7 @@ sealed class ${fragmentFromString(typeName)} {
2282
2432
  const ${fragmentFromString(typeName)}();
2283
2433
  }
2284
2434
 
2285
- ${fragmentFromString(variantClasses.join("\n\n"))}
2435
+ ${fragmentFromString(variantClasses.join("\n\n"))}${pageHasListEnums ? LIST_VALUE_HELPERS : ""}
2286
2436
 
2287
2437
  Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
2288
2438
  return transformEncoder<Map<String, Object?>, ${fragmentFromString(typeName)}>(
@@ -2325,6 +2475,7 @@ function getStructPageFragment(node, scope) {
2325
2475
  const structNode = node.type;
2326
2476
  if (structNode.kind !== "structTypeNode") return emptyFragment();
2327
2477
  const fields = structNode.fields ?? [];
2478
+ let pageHasListStructs = false;
2328
2479
  const fieldManifests = fields.map((f) => ({
2329
2480
  field: f,
2330
2481
  manifest: visit(f.type, scope.typeManifestVisitor)
@@ -2336,10 +2487,13 @@ function getStructPageFragment(node, scope) {
2336
2487
  const ctorSignature = fields.length === 0 ? ` const ${typeName}();` : ` const ${typeName}({
2337
2488
  ${ctorParams}
2338
2489
  });`;
2339
- const eqChecks = fields.length === 0 ? "true" : fields.map(
2340
- (f) => `${camelCase(f.name)} == other.${camelCase(f.name)}`
2341
- ).join(" &&\n ");
2342
- fields.length === 0 ? "runtimeType" : fields.map((f) => camelCase(f.name)).join(", ");
2490
+ const eqChecks = fields.length === 0 ? "true" : fieldManifests.map(({ field: f, manifest }, index) => {
2491
+ const fieldName = camelCase(f.name);
2492
+ return isListManifest(manifest) ? `_listEquals(${fieldName}, other.${fieldName})` : `${fieldName} == other.${fieldName}`;
2493
+ }).join(" &&\n ");
2494
+ if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
2495
+ pageHasListStructs = true;
2496
+ }
2343
2497
  const toStringFields = fields.map(
2344
2498
  (f) => `${camelCase(f.name)}: $${camelCase(f.name)}`
2345
2499
  ).join(", ");
@@ -2389,12 +2543,14 @@ ${fragmentFromString(fieldDecls)}
2389
2543
  ${fragmentFromString(eqChecks)};
2390
2544
 
2391
2545
  @override
2392
- int get hashCode => ${fragmentFromString(getHashExpression(fields.map((f) => camelCase(f.name))))};
2546
+ int get hashCode => ${fragmentFromString(
2547
+ getValueHashExpression(fields, fieldManifests)
2548
+ )};
2393
2549
 
2394
2550
  @override
2395
2551
  String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
2396
2552
  }
2397
-
2553
+ ${pageHasListStructs ? LIST_VALUE_HELPERS : ""}
2398
2554
  Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
2399
2555
  final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
2400
2556
  ${fragmentFromString(encFields)}
@@ -2460,6 +2616,56 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
2460
2616
  return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
2461
2617
  }`;
2462
2618
  }
2619
+ function isListManifest(manifest) {
2620
+ return manifest.type.content.startsWith("List<") || manifest.type.content === "Uint8List";
2621
+ }
2622
+ var LIST_VALUE_HELPERS = `
2623
+ bool _listEquals<T>(List<T>? a, List<T>? b) {
2624
+ if (identical(a, b)) return true;
2625
+ if (a == null || b == null) return a == b;
2626
+ if (a.length != b.length) return false;
2627
+ for (var i = 0; i < a.length; i++) {
2628
+ final left = a[i];
2629
+ final right = b[i];
2630
+ if (left is List<Object?> && right is List<Object?>) {
2631
+ if (!_listEquals(left, right)) return false;
2632
+ } else if (left != right) {
2633
+ return false;
2634
+ }
2635
+ }
2636
+ return true;
2637
+ }
2638
+
2639
+ Object? _deepHash(Object? value) {
2640
+ if (value is List<Object?>) {
2641
+ return Object.hashAll(value.map(_deepHash));
2642
+ }
2643
+ return value;
2644
+ }
2645
+
2646
+ int _listHashCode<T>(List<T>? a) {
2647
+ if (a == null) return 0;
2648
+ return Object.hashAll(a.map(_deepHash));
2649
+ }
2650
+ `;
2651
+ function getValueHashExpression(fields, fieldManifests) {
2652
+ const fieldNames = fields.map(
2653
+ (f) => camelCase(f.name)
2654
+ );
2655
+ const hasListFields = fieldManifests.some(
2656
+ ({ manifest }) => isListManifest(manifest)
2657
+ );
2658
+ if (!hasListFields) {
2659
+ return getHashExpression(fieldNames);
2660
+ }
2661
+ if (fieldNames.length === 1) {
2662
+ return `_listHashCode(${fieldNames[0]})`;
2663
+ }
2664
+ const hashArgs = fields.map(
2665
+ (f, index) => isListManifest(fieldManifests[index].manifest) ? `_listHashCode(${camelCase(f.name)})` : camelCase(f.name)
2666
+ );
2667
+ return `Object.hash(${hashArgs.join(", ")})`;
2668
+ }
2463
2669
  function getHashExpression(fields) {
2464
2670
  if (fields.length === 0) return "runtimeType.hashCode";
2465
2671
  if (fields.length === 1) return `${fields[0]}.hashCode`;
@@ -2533,9 +2739,12 @@ function getRenderMapVisitor(options = {}) {
2533
2739
  visit(node.program, self),
2534
2740
  ...(node.additionalPrograms ?? []).map((p) => visit(p, self))
2535
2741
  ];
2536
- const mergedProgramMap = mergeRenderMaps(programMaps);
2742
+ const mergedProgramMap = mergeRenderMapsWithoutCollisions(programMaps);
2537
2743
  const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
2538
- return mergeRenderMaps([mergedProgramMap, ...categoryMaps]);
2744
+ return mergeRenderMapsWithoutCollisions([
2745
+ mergedProgramMap,
2746
+ ...categoryMaps
2747
+ ]);
2539
2748
  },
2540
2749
  visitProgram(node, { self }) {
2541
2750
  const programName = node.name;
@@ -2597,7 +2806,7 @@ function getRenderMapVisitor(options = {}) {
2597
2806
  );
2598
2807
  maps.push(rootMap);
2599
2808
  }
2600
- return mergeRenderMaps(maps);
2809
+ return mergeRenderMapsWithoutCollisions(maps);
2601
2810
  },
2602
2811
  visitAccount(node, { self }) {
2603
2812
  const fileName = `${snakeCase(node.name)}.dart`;
@@ -2632,6 +2841,18 @@ function getRenderMapVisitor(options = {}) {
2632
2841
  (v) => recordLinkablesOnFirstVisitVisitor(v, linkables)
2633
2842
  );
2634
2843
  }
2844
+ function mergeRenderMapsWithoutCollisions(maps) {
2845
+ const paths = /* @__PURE__ */ new Set();
2846
+ for (const map of maps) {
2847
+ for (const path of map.keys()) {
2848
+ if (paths.has(path)) {
2849
+ throw new Error(`Duplicate generated path "${path}"`);
2850
+ }
2851
+ paths.add(path);
2852
+ }
2853
+ }
2854
+ return mergeRenderMaps(maps);
2855
+ }
2635
2856
  function getSharedCategoryIndexMaps(renderMap) {
2636
2857
  const categories = /* @__PURE__ */ new Map();
2637
2858
  const categoryIndexNames = {
@@ -2648,7 +2869,6 @@ function getSharedCategoryIndexMaps(renderMap) {
2648
2869
  );
2649
2870
  if (!match) continue;
2650
2871
  const [, category, fileName] = match;
2651
- if (fileName === categoryIndexNames[category]) continue;
2652
2872
  if (!categories.has(category)) {
2653
2873
  categories.set(category, /* @__PURE__ */ new Set());
2654
2874
  }
@@ -2678,9 +2898,6 @@ function renderVisitor(outputDir, options = {}) {
2678
2898
  } = options;
2679
2899
  return rootNodeVisitor((root) => {
2680
2900
  const normalizedRoot = normalizeRootNode(root);
2681
- if (deleteFolderBeforeRendering && existsSync(outputDir)) {
2682
- deleteDirectory(outputDir);
2683
- }
2684
2901
  const renderMap = visit(
2685
2902
  normalizedRoot,
2686
2903
  getRenderMapVisitor({ nameApi, dependencyMap })
@@ -2692,12 +2909,9 @@ function renderVisitor(outputDir, options = {}) {
2692
2909
  typePathMap[`definedType:${match[1]}`] = renderPath;
2693
2910
  }
2694
2911
  }
2912
+ const files = [];
2695
2913
  for (const [filePath, frag] of renderMap.entries()) {
2696
2914
  const fullPath = join(outputDir, filePath);
2697
- const dir = dirname(fullPath);
2698
- if (!existsSync(dir)) {
2699
- mkdirSync(dir, { recursive: true });
2700
- }
2701
2915
  const fileDir = dirname(filePath);
2702
2916
  const internalMap = {};
2703
2917
  for (const [key, typePath] of Object.entries(typePathMap)) {
@@ -2712,6 +2926,13 @@ function renderVisitor(outputDir, options = {}) {
2712
2926
  internalMap[key] = rel;
2713
2927
  }
2714
2928
  const content = resolveFragmentContent(frag, dependencyMap ?? {}, internalMap);
2929
+ files.push([fullPath, content]);
2930
+ }
2931
+ if (deleteFolderBeforeRendering && existsSync(outputDir)) {
2932
+ deleteDirectory(outputDir);
2933
+ }
2934
+ for (const [fullPath, content] of files) {
2935
+ mkdirSync(dirname(fullPath), { recursive: true });
2715
2936
  writeFileSync(fullPath, content, "utf-8");
2716
2937
  }
2717
2938
  if (formatCode) {