codama-renderers-dart 0.5.3 → 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 (34) hide show
  1. package/README.md +6 -0
  2. package/dist/index.browser.cjs +176 -107
  3. package/dist/index.browser.cjs.map +1 -1
  4. package/dist/index.browser.js +177 -108
  5. package/dist/index.browser.js.map +1 -1
  6. package/dist/index.node.cjs +176 -107
  7. package/dist/index.node.cjs.map +1 -1
  8. package/dist/index.node.js +177 -108
  9. package/dist/index.node.js.map +1 -1
  10. package/dist/index.react-native.js +177 -108
  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.map +1 -1
  18. package/dist/types/utils/valueNodes.d.ts +4 -0
  19. package/dist/types/utils/valueNodes.d.ts.map +1 -1
  20. package/dist/types/visitors/getRenderMapVisitor.d.ts.map +1 -1
  21. package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
  22. package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
  23. package/package.json +7 -7
  24. package/src/fragments/accountPage.ts +2 -1
  25. package/src/fragments/errorPage.ts +8 -5
  26. package/src/fragments/instructionPage.ts +51 -21
  27. package/src/fragments/pdaPage.ts +7 -14
  28. package/src/fragments/programPage.ts +3 -2
  29. package/src/utils/formatCode.ts +2 -2
  30. package/src/utils/nameTransformers.ts +11 -0
  31. package/src/utils/valueNodes.ts +24 -1
  32. package/src/visitors/getRenderMapVisitor.ts +30 -5
  33. package/src/visitors/getTypeManifestVisitor.ts +100 -75
  34. 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,6 +78,7 @@ 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
  }
83
84
  var DART_RESERVED_WORDS = /* @__PURE__ */ new Set([
@@ -159,6 +160,7 @@ function camelCase(str) {
159
160
  return escapeDartIdentifier(cased);
160
161
  }
161
162
  function snakeCase(str) {
163
+ assertSafeName(str);
162
164
  return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").replace(/_+/g, "_").toLowerCase();
163
165
  }
164
166
  function createDartNameApi() {
@@ -184,6 +186,11 @@ function createDartNameApi() {
184
186
  discriminatorConstant: (name) => `${camelCase(name)}Discriminator`
185
187
  };
186
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
+ }
187
194
 
188
195
  // src/utils/codecs.ts
189
196
  function bytesToDartHexList(bytes) {
@@ -394,10 +401,19 @@ var WELL_KNOWN_ADDRESSES = /* @__PURE__ */ new Map([
394
401
  function getDartValueFragment(value, dartType) {
395
402
  switch (value.kind) {
396
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
+ }
397
410
  return fragmentFromString(
398
411
  dartType === "BigInt" ? `BigInt.from(${value.number})` : String(value.number)
399
412
  );
400
413
  case "booleanValueNode":
414
+ if (typeof value.boolean !== "boolean") {
415
+ throw new Error("Invalid Dart boolean: expected a boolean");
416
+ }
401
417
  return fragmentFromString(String(value.boolean));
402
418
  case "stringValueNode":
403
419
  return fragmentFromString(toDartStringLiteral(value.string));
@@ -441,6 +457,12 @@ function isConstDartValueNode(value) {
441
457
  function toDartStringLiteral(value) {
442
458
  return `'${value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t").replaceAll("\b", "\\b").replaceAll("\f", "\\f").replaceAll("$", "\\$").replaceAll("'", "\\'")}'`;
443
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
+ }
444
466
 
445
467
  // src/utils/discriminators.ts
446
468
  function getDiscriminatorValidationFragment(node, scope) {
@@ -641,23 +663,12 @@ function getTypeManifestVisitor(input) {
641
663
  }
642
664
  );
643
665
  return extendVisitor(baseVisitor, {
644
- visitNumberType(node, { self }) {
645
- const { format, endian } = node;
646
- const endianSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
647
- const endianDecoderSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
648
- const dartType = getNumberDartType(format);
649
- const codecName = getNumberCodecName(format);
666
+ visitNumberType(node) {
650
667
  return {
651
668
  // `BigInt` and other number types live in `dart:core` and need no import.
652
- type: fragmentFromString(dartType),
653
- encoder: fragment`${use(
654
- `get${codecName}Encoder`,
655
- "solanaCodecsNumbers"
656
- )}(${endianSuffix ? fragmentFromString(endianSuffix.slice(2)) : fragmentFromString("")})`,
657
- decoder: fragment`${use(
658
- `get${codecName}Decoder`,
659
- "solanaCodecsNumbers"
660
- )}(${endianDecoderSuffix ? fragmentFromString(endianDecoderSuffix.slice(2)) : fragmentFromString("")})`,
669
+ type: fragmentFromString(getNumberDartType(node.format)),
670
+ encoder: getNumberCodecExpression(node, "Encoder"),
671
+ decoder: getNumberCodecExpression(node, "Decoder"),
661
672
  value: emptyTypeManifest().value,
662
673
  isEnum: false
663
674
  };
@@ -838,7 +849,7 @@ function getTypeManifestVisitor(input) {
838
849
  const fields = nodeFields.map((f) => visit(f, self));
839
850
  const fieldNames = nodeFields.map((f) => f.name);
840
851
  const typeStr = "Map<String, Object?>";
841
- 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(", ");
842
853
  const encoderFrag = fragment`${use(
843
854
  "getStructEncoder",
844
855
  "solanaCodecsDataStructures"
@@ -846,7 +857,7 @@ function getTypeManifestVisitor(input) {
846
857
  for (const f of fields) {
847
858
  encoderFrag.imports.mergeWith(f.encoder.imports);
848
859
  }
849
- 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(", ");
850
861
  const decoderFrag = fragment`${use(
851
862
  "getStructDecoder",
852
863
  "solanaCodecsDataStructures"
@@ -900,8 +911,8 @@ function getTypeManifestVisitor(input) {
900
911
  const itemManifest = visit(node.item, self);
901
912
  const itemTypeStr = itemManifest.type.content;
902
913
  const resolvedPrefix = resolveNestedTypeNode(node.prefix);
903
- const prefixExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Encoder")}` : "";
904
- 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")}` : "";
905
916
  const noneValueExpr = node.fixed ? ", noneValue: const ZeroesNoneValue()" : "";
906
917
  return {
907
918
  type: fragment`${itemManifest.type}?`,
@@ -911,11 +922,11 @@ function getTypeManifestVisitor(input) {
911
922
  )}<${fragmentFromString(itemTypeStr)}>(${use(
912
923
  "transformEncoder",
913
924
  "solanaCodecsCore"
914
- )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
925
+ )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${prefixExpr}${fragmentFromString(noneValueExpr)})`,
915
926
  decoder: fragment`${use(
916
927
  "getNullableDecoder",
917
928
  "solanaCodecsDataStructures"
918
- )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${fragmentFromString(prefixDecoderExpr)}${fragmentFromString(noneValueExpr)})`,
929
+ )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${prefixDecoderExpr}${fragmentFromString(noneValueExpr)})`,
919
930
  value: emptyTypeManifest().value,
920
931
  isEnum: false
921
932
  };
@@ -961,11 +972,11 @@ function getTypeManifestVisitor(input) {
961
972
  encoder: fragment`${use(
962
973
  "fixEncoderSize",
963
974
  "solanaCodecsCore"
964
- )}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))}, allowTruncation: false)`,
975
+ )}(${innerManifest.encoder}, ${fragmentFromString(getNonNegativeInteger(node.size))}, allowTruncation: false)`,
965
976
  decoder: fragment`${use(
966
977
  "fixDecoderSize",
967
978
  "solanaCodecsCore"
968
- )}(${innerManifest.decoder}, ${fragmentFromString(String(node.size))})`,
979
+ )}(${innerManifest.decoder}, ${fragmentFromString(getNonNegativeInteger(node.size))})`,
969
980
  value: innerManifest.value,
970
981
  isEnum: innerManifest.isEnum
971
982
  };
@@ -975,10 +986,10 @@ function getTypeManifestVisitor(input) {
975
986
  const bigIntFormats = /* @__PURE__ */ new Set(["u64", "u128", "i64", "i128"]);
976
987
  let prefixManifest;
977
988
  if (node.prefix.kind === "numberTypeNode" && bigIntFormats.has(node.prefix.format)) {
978
- const codecName = getNumberCodecName(node.prefix.format);
989
+ const numberManifest = visit(node.prefix, self);
979
990
  prefixManifest = {
980
- encoder: fragment`transformEncoder(${use(`get${codecName}Encoder`, "solanaCodecsNumbers")}(), (size) => BigInt.from(size))`,
981
- 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())`
982
993
  };
983
994
  } else {
984
995
  prefixManifest = visit(node.prefix, self);
@@ -1069,10 +1080,10 @@ function getTypeManifestVisitor(input) {
1069
1080
  };
1070
1081
  },
1071
1082
  visitPreOffsetType(node, { self }) {
1072
- return visit(node.type, self);
1083
+ return getOffsetManifest(node, self);
1073
1084
  },
1074
1085
  visitPostOffsetType(node, { self }) {
1075
- return visit(node.type, self);
1086
+ return getOffsetManifest(node, self);
1076
1087
  },
1077
1088
  visitSolAmountType(node, { self }) {
1078
1089
  return visit(node.number, self);
@@ -1161,16 +1172,17 @@ function getNumberCodecName(format) {
1161
1172
  return "U8";
1162
1173
  }
1163
1174
  }
1164
- function getNumberCodecExpression(format, codecType) {
1165
- const name = getNumberCodecName(format);
1166
- 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})`;
1167
1179
  }
1168
1180
  function getArraySizeExpression(node, codecType) {
1169
1181
  if (!("count" in node) || !node.count) return "";
1170
1182
  const count = node.count;
1171
1183
  switch (count.kind) {
1172
1184
  case "fixedCountNode":
1173
- return `, size: FixedArraySize(${count.value})`;
1185
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1174
1186
  case "remainderCountNode":
1175
1187
  return ", size: RemainderArraySize()";
1176
1188
  case "prefixedCountNode": {
@@ -1178,7 +1190,7 @@ function getArraySizeExpression(node, codecType) {
1178
1190
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1179
1191
  return "";
1180
1192
  }
1181
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1193
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1182
1194
  }
1183
1195
  default:
1184
1196
  return "";
@@ -1189,7 +1201,7 @@ function getMapSizeExpression(node, codecType) {
1189
1201
  const count = node.count;
1190
1202
  switch (count.kind) {
1191
1203
  case "fixedCountNode":
1192
- return `, size: FixedArraySize(${count.value})`;
1204
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1193
1205
  case "remainderCountNode":
1194
1206
  return ", size: RemainderArraySize()";
1195
1207
  case "prefixedCountNode": {
@@ -1197,7 +1209,7 @@ function getMapSizeExpression(node, codecType) {
1197
1209
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1198
1210
  return "";
1199
1211
  }
1200
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1212
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1201
1213
  }
1202
1214
  default:
1203
1215
  return "";
@@ -1208,7 +1220,7 @@ function getSetSizeExpression(node, codecType) {
1208
1220
  const count = node.count;
1209
1221
  switch (count.kind) {
1210
1222
  case "fixedCountNode":
1211
- return `, size: FixedArraySize(${count.value})`;
1223
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1212
1224
  case "remainderCountNode":
1213
1225
  return ", size: RemainderArraySize()";
1214
1226
  case "prefixedCountNode": {
@@ -1216,7 +1228,7 @@ function getSetSizeExpression(node, codecType) {
1216
1228
  if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1217
1229
  return "";
1218
1230
  }
1219
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1231
+ return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
1220
1232
  }
1221
1233
  default:
1222
1234
  return "";
@@ -1227,36 +1239,61 @@ function getHiddenAffixManifest(node, self) {
1227
1239
  const manifest = visit(node, self);
1228
1240
  return { encoder: manifest.encoder, decoder: manifest.decoder };
1229
1241
  }
1230
- const constantBytes = getConstantBytesExpression(node);
1242
+ const constantBytes = getConstantBytesExpression(node, self);
1231
1243
  return {
1232
1244
  encoder: fragment`${use("getConstantEncoder", "solanaCodecsDataStructures")}(${constantBytes})`,
1233
1245
  decoder: fragment`${use("getConstantDecoder", "solanaCodecsDataStructures")}(${constantBytes})`
1234
1246
  };
1235
1247
  }
1236
- function getConstantBytesExpression(node) {
1237
- const valueNode = node.value;
1238
- const typeNode = node.type;
1239
- if (valueNode?.kind !== "numberValueNode") {
1248
+ function getConstantBytesExpression(node, self) {
1249
+ if (node.value.kind !== "numberValueNode") {
1240
1250
  throw new Error(
1241
- `Unsupported hidden affix constant value kind: ${valueNode?.kind}`
1251
+ `Unsupported hidden affix constant value kind: ${node.value.kind}`
1242
1252
  );
1243
1253
  }
1244
- const value = Number(valueNode.number);
1245
- if (typeNode?.kind === "numberTypeNode") {
1246
- 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}`);
1247
1257
  }
1248
- if (typeNode?.kind === "preOffsetTypeNode") {
1249
- const inner = typeNode.type;
1250
- if (inner?.kind !== "numberTypeNode") {
1251
- throw new Error(
1252
- `Unsupported hidden affix pre-offset inner type kind: ${inner?.kind}`
1253
- );
1254
- }
1255
- 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");
1256
1264
  }
1257
- throw new Error(
1258
- `Unsupported hidden affix constant type kind: ${typeNode?.kind}`
1259
- );
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
+ };
1260
1297
  }
1261
1298
 
1262
1299
  // src/fragments/discriminatorConstants.ts
@@ -1389,7 +1426,7 @@ ${ctorParams}
1389
1426
  const decodeFnName = scope.nameApi.accountDecodeFunction(name);
1390
1427
  const sizeFragment = node.size != null ? fragment`
1391
1428
  /// The size of the [${fragmentFromString(typeName)}] account data in bytes.
1392
- 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();
1393
1430
  const discFragment = getDiscriminatorConstantsFragment(node, scope);
1394
1431
  const discriminatorValidation = getDiscriminatorValidationFragment(
1395
1432
  node,
@@ -1503,9 +1540,10 @@ function getErrorPageFragment(programNode, scope) {
1503
1540
  programName,
1504
1541
  err.name
1505
1542
  );
1543
+ getNonNegativeInteger(err.code);
1506
1544
  const hexCode = `0x${err.code.toString(16)}`;
1507
- const docs = err.docs?.length ? err.docs.map((d) => `/// ${d}`).join("\n") + "\n" : "";
1508
- 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}"`)}
1509
1547
  ` : "";
1510
1548
  return `${docs}${message}const int ${constName} = ${hexCode}; // ${err.code}`;
1511
1549
  }).join("\n\n");
@@ -1515,7 +1553,7 @@ function getErrorPageFragment(programNode, scope) {
1515
1553
  err.name
1516
1554
  );
1517
1555
  const message = err.message ?? err.name;
1518
- return ` ${constName}: '${escapeString(message)}',`;
1556
+ return ` ${constName}: ${toDartStringLiteral(message)},`;
1519
1557
  }).join("\n");
1520
1558
  const errorMessageFnName = scope.nameApi.errorMessageFunction(programName);
1521
1559
  const isErrorFnName = `is${pascalCase(programName)}Error`;
@@ -1541,8 +1579,8 @@ bool ${fragmentFromString(isErrorFnName)}(int code) {
1541
1579
  return _${fragmentFromString(camelCase(programName))}ErrorMessages.containsKey(code);
1542
1580
  }`;
1543
1581
  }
1544
- function escapeString(str) {
1545
- return str.replace(/'/g, "\\'").replace(/\n/g, "\\n");
1582
+ function toDartDocComment(value) {
1583
+ return value.split(/\r\n?|\n/).map((line) => `/// ${line}`).join("\n");
1546
1584
  }
1547
1585
 
1548
1586
  // src/fragments/indexPage.ts
@@ -1668,14 +1706,28 @@ ${dataCtorInitializers}` : ""};`;
1668
1706
  const dataEncoderName = `get${typeName}InstructionDataEncoder`;
1669
1707
  const dataDecoderName = `get${typeName}InstructionDataDecoder`;
1670
1708
  const dataCodecName = `get${typeName}InstructionDataCodec`;
1671
- const argNames = new Set(args.map((a) => camelCase(a.name)));
1672
- const accountNames = new Set(accounts.map((a) => camelCase(a.name)));
1673
- const hasProgramAddressCollision = argNames.has("programAddress") || accountNames.has("programAddress");
1674
- const instrProgramParam = hasProgramAddressCollision ? "instructionProgramAddress" : "programAddress";
1675
- let instructionDataLocal = "instructionData";
1676
- while (argNames.has(instructionDataLocal) || accountNames.has(instructionDataLocal)) {
1677
- instructionDataLocal = `${instructionDataLocal}_`;
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
+ }
1678
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");
1679
1731
  const accountParams = accounts.map((acc) => {
1680
1732
  const fieldName = camelCase(acc.name);
1681
1733
  const isRequired = !(acc.isOptional ?? false);
@@ -1695,7 +1747,7 @@ ${dataCtorInitializers}` : ""};`;
1695
1747
  }).join("\n");
1696
1748
  const accountMetas = accounts.map((acc) => {
1697
1749
  const fieldName = camelCase(acc.name);
1698
- const role = getAccountRole(acc);
1750
+ const role = getAccountRole(acc, signerParams.get(acc));
1699
1751
  const isOptional = acc.isOptional ?? false;
1700
1752
  if (isOptional) {
1701
1753
  if (optionalAccountStrategy === "omitted") {
@@ -1778,11 +1830,13 @@ Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}
1778
1830
  return combineCodec(${fragmentFromString(dataEncoderName)}(), ${fragmentFromString(dataDecoderName)}());
1779
1831
  }`);
1780
1832
  parts.push(fragment`
1781
- /// Creates a [${fragmentFromString(typeName)}] instruction.
1833
+ /// Creates a [${fragmentFromString(typeName)}] instruction.${fragmentFromString(signerParamDocs ? `
1834
+ ${signerParamDocs}` : "")}
1782
1835
  Instruction ${fragmentFromString(instrFnName)}({
1783
1836
  required Address ${fragmentFromString(instrProgramParam)},
1784
1837
  ${fragmentFromString(accountParams)}
1785
- ${fragmentFromString(argParams)}
1838
+ ${fragmentFromString(argParams)}${fragmentFromString(signerParamDeclarations ? `
1839
+ ${signerParamDeclarations}` : "")}
1786
1840
  }) {
1787
1841
  final ${instructionDataLocal} = ${fragmentFromString(dataClassName)}(
1788
1842
  ${fragmentFromString(dataConstruction)}
@@ -1812,13 +1866,20 @@ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruct
1812
1866
  }
1813
1867
  return result;
1814
1868
  }
1815
- function getAccountRole(acc) {
1816
- const isSigner = acc.isSigner === true || acc.isSigner === "either";
1817
- const isWritable = acc.isWritable ?? false;
1818
- if (isSigner && isWritable) return "AccountRole.writableSigner";
1819
- if (isSigner) return "AccountRole.readonlySigner";
1820
- if (isWritable) return "AccountRole.writable";
1821
- 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;
1822
1883
  }
1823
1884
  function isDiscriminatorArg(arg, node) {
1824
1885
  const discriminators = node.discriminators ?? [];
@@ -1871,9 +1932,9 @@ function getPdaPageFragment(node, scope) {
1871
1932
  for (const seed of seeds) {
1872
1933
  if (seed.kind === "constantPdaSeedNode") {
1873
1934
  if (seed.value.kind === "bytesValueNode") {
1874
- seedValues.push(` Uint8List.fromList(${bytesValueToDart(seed.value.data)}),`);
1935
+ seedValues.push(` ${getDartValueFragment(seed.value).content},`);
1875
1936
  } else if (seed.value.kind === "stringValueNode") {
1876
- seedValues.push(` '${seed.value.string}',`);
1937
+ seedValues.push(` ${toDartStringLiteral(seed.value.string)},`);
1877
1938
  } else if (seed.value.kind === "publicKeyValueNode") {
1878
1939
  const wellKnownSeedName = WELL_KNOWN_ADDRESSES.get(seed.value.publicKey);
1879
1940
  if (wellKnownSeedName) {
@@ -1883,13 +1944,14 @@ function getPdaPageFragment(node, scope) {
1883
1944
  );
1884
1945
  } else {
1885
1946
  seedValues.push(
1886
- ` getAddressEncoder().encode(Address('${seed.value.publicKey}')),`
1947
+ ` getAddressEncoder().encode(Address(${toDartStringLiteral(seed.value.publicKey)})),`
1887
1948
  );
1888
1949
  }
1889
1950
  } else if (seed.value.kind === "numberValueNode") {
1890
1951
  const manifest = visit(seed.type, scope.typeManifestVisitor);
1891
1952
  seedManifests.push(manifest);
1892
- 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}),`);
1893
1955
  }
1894
1956
  } else if (seed.kind === "variablePdaSeedNode") {
1895
1957
  const manifest = visit(seed.type, scope.typeManifestVisitor);
@@ -1903,7 +1965,7 @@ function getPdaPageFragment(node, scope) {
1903
1965
  if (wellKnownProgramName) {
1904
1966
  use(wellKnownProgramName, "solanaAddresses");
1905
1967
  }
1906
- 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";
1907
1969
  const typedDataImport = hasByteSeeds ? use("Uint8List", "dartTypedData") : fragmentFromString("");
1908
1970
  const metaImport = hasSeedsClass ? use("immutable", "meta") : fragmentFromString("");
1909
1971
  const parts = [
@@ -1950,14 +2012,6 @@ ${fragmentFromString(seedValues.join("\n"))}
1950
2012
  }
1951
2013
  return result;
1952
2014
  }
1953
- function bytesValueToDart(hex) {
1954
- const clean = hex.replace(/^0x/, "");
1955
- const pairs = [];
1956
- for (let i = 0; i < clean.length; i += 2) {
1957
- pairs.push(`0x${clean.slice(i, i + 2)}`);
1958
- }
1959
- return `[${pairs.join(", ")}]`;
1960
- }
1961
2015
  function getProgramPageFragment(node, scope) {
1962
2016
  const name = node.name;
1963
2017
  const addressConstName = scope.nameApi.programAddressConstant(name);
@@ -1978,7 +2032,7 @@ const ${fragmentFromString(addressConstName)} = ${fragmentFromString(wellKnownNa
1978
2032
  ${use("Address", "solanaAddresses")}
1979
2033
 
1980
2034
  /// The address of the ${fragmentFromString(pascalCase(name))} program.
1981
- const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}');`;
2035
+ const ${fragmentFromString(addressConstName)} = Address(${fragmentFromString(toDartStringLiteral(node.publicKey))});`;
1982
2036
  const parts = [addressDeclaration];
1983
2037
  const accounts = node.accounts ?? [];
1984
2038
  if (accounts.length > 0) {
@@ -2119,7 +2173,7 @@ function getValueFragment(value, dartType) {
2119
2173
  case "booleanValueNode":
2120
2174
  return fragmentFromString(String(value.boolean));
2121
2175
  case "stringValueNode":
2122
- return fragmentFromString(`'${value.string.replaceAll("'", "\\'")}'`);
2176
+ return fragmentFromString(toDartStringLiteral(value.string));
2123
2177
  case "bytesValueNode": {
2124
2178
  const clean = value.data.replace(/^0x/, "");
2125
2179
  const bytes = clean.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [];
@@ -2685,9 +2739,12 @@ function getRenderMapVisitor(options = {}) {
2685
2739
  visit(node.program, self),
2686
2740
  ...(node.additionalPrograms ?? []).map((p) => visit(p, self))
2687
2741
  ];
2688
- const mergedProgramMap = mergeRenderMaps(programMaps);
2742
+ const mergedProgramMap = mergeRenderMapsWithoutCollisions(programMaps);
2689
2743
  const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
2690
- return mergeRenderMaps([mergedProgramMap, ...categoryMaps]);
2744
+ return mergeRenderMapsWithoutCollisions([
2745
+ mergedProgramMap,
2746
+ ...categoryMaps
2747
+ ]);
2691
2748
  },
2692
2749
  visitProgram(node, { self }) {
2693
2750
  const programName = node.name;
@@ -2749,7 +2806,7 @@ function getRenderMapVisitor(options = {}) {
2749
2806
  );
2750
2807
  maps.push(rootMap);
2751
2808
  }
2752
- return mergeRenderMaps(maps);
2809
+ return mergeRenderMapsWithoutCollisions(maps);
2753
2810
  },
2754
2811
  visitAccount(node, { self }) {
2755
2812
  const fileName = `${snakeCase(node.name)}.dart`;
@@ -2784,6 +2841,18 @@ function getRenderMapVisitor(options = {}) {
2784
2841
  (v) => recordLinkablesOnFirstVisitVisitor(v, linkables)
2785
2842
  );
2786
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
+ }
2787
2856
  function getSharedCategoryIndexMaps(renderMap) {
2788
2857
  const categories = /* @__PURE__ */ new Map();
2789
2858
  const categoryIndexNames = {
@@ -2800,7 +2869,6 @@ function getSharedCategoryIndexMaps(renderMap) {
2800
2869
  );
2801
2870
  if (!match) continue;
2802
2871
  const [, category, fileName] = match;
2803
- if (fileName === categoryIndexNames[category]) continue;
2804
2872
  if (!categories.has(category)) {
2805
2873
  categories.set(category, /* @__PURE__ */ new Set());
2806
2874
  }
@@ -2830,9 +2898,6 @@ function renderVisitor(outputDir, options = {}) {
2830
2898
  } = options;
2831
2899
  return rootNodeVisitor((root) => {
2832
2900
  const normalizedRoot = normalizeRootNode(root);
2833
- if (deleteFolderBeforeRendering && existsSync(outputDir)) {
2834
- deleteDirectory(outputDir);
2835
- }
2836
2901
  const renderMap = visit(
2837
2902
  normalizedRoot,
2838
2903
  getRenderMapVisitor({ nameApi, dependencyMap })
@@ -2844,12 +2909,9 @@ function renderVisitor(outputDir, options = {}) {
2844
2909
  typePathMap[`definedType:${match[1]}`] = renderPath;
2845
2910
  }
2846
2911
  }
2912
+ const files = [];
2847
2913
  for (const [filePath, frag] of renderMap.entries()) {
2848
2914
  const fullPath = join(outputDir, filePath);
2849
- const dir = dirname(fullPath);
2850
- if (!existsSync(dir)) {
2851
- mkdirSync(dir, { recursive: true });
2852
- }
2853
2915
  const fileDir = dirname(filePath);
2854
2916
  const internalMap = {};
2855
2917
  for (const [key, typePath] of Object.entries(typePathMap)) {
@@ -2864,6 +2926,13 @@ function renderVisitor(outputDir, options = {}) {
2864
2926
  internalMap[key] = rel;
2865
2927
  }
2866
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 });
2867
2936
  writeFileSync(fullPath, content, "utf-8");
2868
2937
  }
2869
2938
  if (formatCode) {