codama-renderers-dart 0.5.3 → 0.5.5

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 (35) hide show
  1. package/README.md +6 -0
  2. package/dist/index.browser.cjs +195 -126
  3. package/dist/index.browser.cjs.map +1 -1
  4. package/dist/index.browser.js +196 -127
  5. package/dist/index.browser.js.map +1 -1
  6. package/dist/index.node.cjs +195 -126
  7. package/dist/index.node.cjs.map +1 -1
  8. package/dist/index.node.js +196 -127
  9. package/dist/index.node.js.map +1 -1
  10. package/dist/index.react-native.js +196 -127
  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 +11 -11
  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 +127 -87
  34. package/src/visitors/renderVisitor.ts +11 -13
  35. package/LICENSE +0 -21
@@ -12,7 +12,7 @@ var errors = require('@codama/errors');
12
12
 
13
13
  // src/visitors/renderVisitor.ts
14
14
  function formatDartDirectory(dir) {
15
- child_process.execSync(`dart format "${dir}"`, {
15
+ child_process.execFileSync("dart", ["format", "--", dir], {
16
16
  encoding: "utf-8",
17
17
  timeout: 6e4,
18
18
  stdio: ["pipe", "pipe", "pipe"]
@@ -82,6 +82,7 @@ function setEmptyCollection(record, key) {
82
82
 
83
83
  // src/utils/nameTransformers.ts
84
84
  function pascalCase(str) {
85
+ assertSafeName(str);
85
86
  return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
86
87
  }
87
88
  var DART_RESERVED_WORDS = /* @__PURE__ */ new Set([
@@ -163,6 +164,7 @@ function camelCase(str) {
163
164
  return escapeDartIdentifier(cased);
164
165
  }
165
166
  function snakeCase(str) {
167
+ assertSafeName(str);
166
168
  return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").replace(/_+/g, "_").toLowerCase();
167
169
  }
168
170
  function createDartNameApi() {
@@ -188,6 +190,11 @@ function createDartNameApi() {
188
190
  discriminatorConstant: (name) => `${camelCase(name)}Discriminator`
189
191
  };
190
192
  }
193
+ function assertSafeName(name) {
194
+ if (!/^[a-zA-Z0-9_ -]*$/.test(name)) {
195
+ throw new Error(`Invalid Dart node name: ${JSON.stringify(name)}`);
196
+ }
197
+ }
191
198
 
192
199
  // src/utils/codecs.ts
193
200
  function bytesToDartHexList(bytes) {
@@ -398,10 +405,19 @@ var WELL_KNOWN_ADDRESSES = /* @__PURE__ */ new Map([
398
405
  function getDartValueFragment(value, dartType) {
399
406
  switch (value.kind) {
400
407
  case "numberValueNode":
408
+ if (typeof value.number !== "number" || !Number.isFinite(value.number)) {
409
+ throw new Error("Invalid Dart number: expected a finite number");
410
+ }
411
+ if (dartType === "BigInt" && !Number.isSafeInteger(value.number)) {
412
+ throw new Error("Invalid Dart BigInt: expected a safe integer");
413
+ }
401
414
  return fragmentFromString(
402
415
  dartType === "BigInt" ? `BigInt.from(${value.number})` : String(value.number)
403
416
  );
404
417
  case "booleanValueNode":
418
+ if (typeof value.boolean !== "boolean") {
419
+ throw new Error("Invalid Dart boolean: expected a boolean");
420
+ }
405
421
  return fragmentFromString(String(value.boolean));
406
422
  case "stringValueNode":
407
423
  return fragmentFromString(toDartStringLiteral(value.string));
@@ -445,6 +461,12 @@ function isConstDartValueNode(value) {
445
461
  function toDartStringLiteral(value) {
446
462
  return `'${value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t").replaceAll("\b", "\\b").replaceAll("\f", "\\f").replaceAll("$", "\\$").replaceAll("'", "\\'")}'`;
447
463
  }
464
+ function getNonNegativeInteger(value) {
465
+ if (!Number.isSafeInteger(value) || value < 0) {
466
+ throw new Error("Expected a non-negative safe integer value");
467
+ }
468
+ return String(value);
469
+ }
448
470
 
449
471
  // src/utils/discriminators.ts
450
472
  function getDiscriminatorValidationFragment(node, scope) {
@@ -645,23 +667,12 @@ function getTypeManifestVisitor(input) {
645
667
  }
646
668
  );
647
669
  return visitorsCore.extendVisitor(baseVisitor, {
648
- visitNumberType(node, { self }) {
649
- const { format, endian } = node;
650
- const endianSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
651
- const endianDecoderSuffix = endian === "be" ? ", NumberCodecConfig(endian: Endian.big)" : "";
652
- const dartType = getNumberDartType(format);
653
- const codecName = getNumberCodecName(format);
670
+ visitNumberType(node) {
654
671
  return {
655
672
  // `BigInt` and other number types live in `dart:core` and need no import.
656
- type: fragmentFromString(dartType),
657
- encoder: fragment`${use(
658
- `get${codecName}Encoder`,
659
- "solanaCodecsNumbers"
660
- )}(${endianSuffix ? fragmentFromString(endianSuffix.slice(2)) : fragmentFromString("")})`,
661
- decoder: fragment`${use(
662
- `get${codecName}Decoder`,
663
- "solanaCodecsNumbers"
664
- )}(${endianDecoderSuffix ? fragmentFromString(endianDecoderSuffix.slice(2)) : fragmentFromString("")})`,
673
+ type: fragmentFromString(getNumberDartType(node.format)),
674
+ encoder: getNumberCodecExpression(node, "Encoder"),
675
+ decoder: getNumberCodecExpression(node, "Decoder"),
665
676
  value: emptyTypeManifest().value,
666
677
  isEnum: false
667
678
  };
@@ -736,8 +747,8 @@ function getTypeManifestVisitor(input) {
736
747
  },
737
748
  visitArrayType(node, { self }) {
738
749
  const itemManifest = visitorsCore.visit(node.item, self);
739
- const encoderSizeExpr = getArraySizeExpression(node, "Encoder");
740
- const decoderSizeExpr = getArraySizeExpression(node, "Decoder");
750
+ const encoderSizeExpr = getArraySizeExpression(node, "Encoder", self);
751
+ const decoderSizeExpr = getArraySizeExpression(node, "Decoder", self);
741
752
  const itemType = fragmentFromString(itemManifest.type.content);
742
753
  const itemEncoder = fragment`${use(
743
754
  "transformEncoder",
@@ -760,8 +771,8 @@ function getTypeManifestVisitor(input) {
760
771
  visitMapType(node, { self }) {
761
772
  const keyManifest = visitorsCore.visit(node.key, self);
762
773
  const valueManifest = visitorsCore.visit(node.value, self);
763
- const encoderSizeExpr = getMapSizeExpression(node, "Encoder");
764
- const decoderSizeExpr = getMapSizeExpression(node, "Decoder");
774
+ const encoderSizeExpr = getMapSizeExpression(node, "Encoder", self);
775
+ const decoderSizeExpr = getMapSizeExpression(node, "Decoder", self);
765
776
  return {
766
777
  type: fragment`Map<${keyManifest.type}, ${valueManifest.type}>`,
767
778
  encoder: fragment`${use(
@@ -778,8 +789,8 @@ function getTypeManifestVisitor(input) {
778
789
  },
779
790
  visitSetType(node, { self }) {
780
791
  const itemManifest = visitorsCore.visit(node.item, self);
781
- const encoderSizeExpr = getSetSizeExpression(node, "Encoder");
782
- const decoderSizeExpr = getSetSizeExpression(node, "Decoder");
792
+ const encoderSizeExpr = getSetSizeExpression(node, "Encoder", self);
793
+ const decoderSizeExpr = getSetSizeExpression(node, "Decoder", self);
783
794
  return {
784
795
  type: fragment`Set<${itemManifest.type}>`,
785
796
  encoder: fragment`${use(
@@ -842,7 +853,7 @@ function getTypeManifestVisitor(input) {
842
853
  const fields = nodeFields.map((f) => visitorsCore.visit(f, self));
843
854
  const fieldNames = nodeFields.map((f) => f.name);
844
855
  const typeStr = "Map<String, Object?>";
845
- const encoderFields = fields.map((f, i) => `('${fieldNames[i]}', ${f.encoder.content})`).join(", ");
856
+ const encoderFields = fields.map((f, i) => `(${toDartStringLiteral(fieldNames[i])}, ${f.encoder.content})`).join(", ");
846
857
  const encoderFrag = fragment`${use(
847
858
  "getStructEncoder",
848
859
  "solanaCodecsDataStructures"
@@ -850,7 +861,7 @@ function getTypeManifestVisitor(input) {
850
861
  for (const f of fields) {
851
862
  encoderFrag.imports.mergeWith(f.encoder.imports);
852
863
  }
853
- const decoderFields = fields.map((f, i) => `('${fieldNames[i]}', ${f.decoder.content})`).join(", ");
864
+ const decoderFields = fields.map((f, i) => `(${toDartStringLiteral(fieldNames[i])}, ${f.decoder.content})`).join(", ");
854
865
  const decoderFrag = fragment`${use(
855
866
  "getStructDecoder",
856
867
  "solanaCodecsDataStructures"
@@ -904,8 +915,8 @@ function getTypeManifestVisitor(input) {
904
915
  const itemManifest = visitorsCore.visit(node.item, self);
905
916
  const itemTypeStr = itemManifest.type.content;
906
917
  const resolvedPrefix = nodes.resolveNestedTypeNode(node.prefix);
907
- const prefixExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Encoder")}` : "";
908
- const prefixDecoderExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Decoder")}` : "";
918
+ const prefixExpr = resolvedPrefix.format !== "u8" ? fragment`, prefix: ${getNumberCodecExpression(resolvedPrefix, "Encoder")}` : "";
919
+ const prefixDecoderExpr = resolvedPrefix.format !== "u8" ? fragment`, prefix: ${getNumberCodecExpression(resolvedPrefix, "Decoder")}` : "";
909
920
  const noneValueExpr = node.fixed ? ", noneValue: const ZeroesNoneValue()" : "";
910
921
  return {
911
922
  type: fragment`${itemManifest.type}?`,
@@ -915,11 +926,11 @@ function getTypeManifestVisitor(input) {
915
926
  )}<${fragmentFromString(itemTypeStr)}>(${use(
916
927
  "transformEncoder",
917
928
  "solanaCodecsCore"
918
- )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
929
+ )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${prefixExpr}${fragmentFromString(noneValueExpr)})`,
919
930
  decoder: fragment`${use(
920
931
  "getNullableDecoder",
921
932
  "solanaCodecsDataStructures"
922
- )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${fragmentFromString(prefixDecoderExpr)}${fragmentFromString(noneValueExpr)})`,
933
+ )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${prefixDecoderExpr}${fragmentFromString(noneValueExpr)})`,
923
934
  value: emptyTypeManifest().value,
924
935
  isEnum: false
925
936
  };
@@ -965,11 +976,11 @@ function getTypeManifestVisitor(input) {
965
976
  encoder: fragment`${use(
966
977
  "fixEncoderSize",
967
978
  "solanaCodecsCore"
968
- )}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))}, allowTruncation: false)`,
979
+ )}(${innerManifest.encoder}, ${fragmentFromString(getNonNegativeInteger(node.size))}, allowTruncation: false)`,
969
980
  decoder: fragment`${use(
970
981
  "fixDecoderSize",
971
982
  "solanaCodecsCore"
972
- )}(${innerManifest.decoder}, ${fragmentFromString(String(node.size))})`,
983
+ )}(${innerManifest.decoder}, ${fragmentFromString(getNonNegativeInteger(node.size))})`,
973
984
  value: innerManifest.value,
974
985
  isEnum: innerManifest.isEnum
975
986
  };
@@ -979,10 +990,10 @@ function getTypeManifestVisitor(input) {
979
990
  const bigIntFormats = /* @__PURE__ */ new Set(["u64", "u128", "i64", "i128"]);
980
991
  let prefixManifest;
981
992
  if (node.prefix.kind === "numberTypeNode" && bigIntFormats.has(node.prefix.format)) {
982
- const codecName = getNumberCodecName(node.prefix.format);
993
+ const numberManifest = visitorsCore.visit(node.prefix, self);
983
994
  prefixManifest = {
984
- encoder: fragment`transformEncoder(${use(`get${codecName}Encoder`, "solanaCodecsNumbers")}(), (size) => BigInt.from(size))`,
985
- decoder: fragment`transformDecoder(${use(`get${codecName}Decoder`, "solanaCodecsNumbers")}(), (size, _, __) => size.toInt())`
995
+ encoder: fragment`${use("transformEncoder", "solanaCodecsCore")}(${numberManifest.encoder}, (size) => BigInt.from(size))`,
996
+ decoder: fragment`${use("transformDecoder", "solanaCodecsCore")}(${numberManifest.decoder}, (size, _, __) => size.toInt())`
986
997
  };
987
998
  } else {
988
999
  prefixManifest = visitorsCore.visit(node.prefix, self);
@@ -1073,10 +1084,10 @@ function getTypeManifestVisitor(input) {
1073
1084
  };
1074
1085
  },
1075
1086
  visitPreOffsetType(node, { self }) {
1076
- return visitorsCore.visit(node.type, self);
1087
+ return getOffsetManifest(node, self);
1077
1088
  },
1078
1089
  visitPostOffsetType(node, { self }) {
1079
- return visitorsCore.visit(node.type, self);
1090
+ return getOffsetManifest(node, self);
1080
1091
  },
1081
1092
  visitSolAmountType(node, { self }) {
1082
1093
  return visitorsCore.visit(node.number, self);
@@ -1165,62 +1176,63 @@ function getNumberCodecName(format) {
1165
1176
  return "U8";
1166
1177
  }
1167
1178
  }
1168
- function getNumberCodecExpression(format, codecType) {
1169
- const name = getNumberCodecName(format);
1170
- return `get${name}${codecType}()`;
1179
+ function getNumberCodecExpression(node, codecType) {
1180
+ const name = getNumberCodecName(node.format);
1181
+ const config = node.endian === "be" ? fragment`${use("NumberCodecConfig", "solanaCodecsNumbers")}(endian: ${use("Endian", "dartTypedData")}.big)` : "";
1182
+ return fragment`${use(`get${name}${codecType}`, "solanaCodecsNumbers")}(${config})`;
1171
1183
  }
1172
- function getArraySizeExpression(node, codecType) {
1184
+ function getArraySizeExpression(node, codecType, self) {
1173
1185
  if (!("count" in node) || !node.count) return "";
1174
1186
  const count = node.count;
1175
1187
  switch (count.kind) {
1176
1188
  case "fixedCountNode":
1177
- return `, size: FixedArraySize(${count.value})`;
1189
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1178
1190
  case "remainderCountNode":
1179
1191
  return ", size: RemainderArraySize()";
1180
1192
  case "prefixedCountNode": {
1181
- const resolvedPrefix = nodes.resolveNestedTypeNode(count.prefix);
1182
- if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1193
+ if (count.prefix.kind === "numberTypeNode" && count.prefix.format === "u32" && (!count.prefix.endian || count.prefix.endian === "le")) {
1183
1194
  return "";
1184
1195
  }
1185
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1196
+ const prefix = visitorsCore.visit(count.prefix, self);
1197
+ return fragment`, size: PrefixedArraySize(${codecType === "Encoder" ? prefix.encoder : prefix.decoder})`;
1186
1198
  }
1187
1199
  default:
1188
1200
  return "";
1189
1201
  }
1190
1202
  }
1191
- function getMapSizeExpression(node, codecType) {
1203
+ function getMapSizeExpression(node, codecType, self) {
1192
1204
  if (!("count" in node) || !node.count) return "";
1193
1205
  const count = node.count;
1194
1206
  switch (count.kind) {
1195
1207
  case "fixedCountNode":
1196
- return `, size: FixedArraySize(${count.value})`;
1208
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1197
1209
  case "remainderCountNode":
1198
1210
  return ", size: RemainderArraySize()";
1199
1211
  case "prefixedCountNode": {
1200
- const resolvedPrefix = nodes.resolveNestedTypeNode(count.prefix);
1201
- if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1212
+ if (count.prefix.kind === "numberTypeNode" && count.prefix.format === "u32" && (!count.prefix.endian || count.prefix.endian === "le")) {
1202
1213
  return "";
1203
1214
  }
1204
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1215
+ const prefix = visitorsCore.visit(count.prefix, self);
1216
+ return fragment`, size: PrefixedArraySize(${codecType === "Encoder" ? prefix.encoder : prefix.decoder})`;
1205
1217
  }
1206
1218
  default:
1207
1219
  return "";
1208
1220
  }
1209
1221
  }
1210
- function getSetSizeExpression(node, codecType) {
1222
+ function getSetSizeExpression(node, codecType, self) {
1211
1223
  if (!("count" in node) || !node.count) return "";
1212
1224
  const count = node.count;
1213
1225
  switch (count.kind) {
1214
1226
  case "fixedCountNode":
1215
- return `, size: FixedArraySize(${count.value})`;
1227
+ return `, size: FixedArraySize(${getNonNegativeInteger(count.value)})`;
1216
1228
  case "remainderCountNode":
1217
1229
  return ", size: RemainderArraySize()";
1218
1230
  case "prefixedCountNode": {
1219
- const resolvedPrefix = nodes.resolveNestedTypeNode(count.prefix);
1220
- if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
1231
+ if (count.prefix.kind === "numberTypeNode" && count.prefix.format === "u32" && (!count.prefix.endian || count.prefix.endian === "le")) {
1221
1232
  return "";
1222
1233
  }
1223
- return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix.format, codecType)})`;
1234
+ const prefix = visitorsCore.visit(count.prefix, self);
1235
+ return fragment`, size: PrefixedArraySize(${codecType === "Encoder" ? prefix.encoder : prefix.decoder})`;
1224
1236
  }
1225
1237
  default:
1226
1238
  return "";
@@ -1231,36 +1243,61 @@ function getHiddenAffixManifest(node, self) {
1231
1243
  const manifest = visitorsCore.visit(node, self);
1232
1244
  return { encoder: manifest.encoder, decoder: manifest.decoder };
1233
1245
  }
1234
- const constantBytes = getConstantBytesExpression(node);
1246
+ const constantBytes = getConstantBytesExpression(node, self);
1235
1247
  return {
1236
1248
  encoder: fragment`${use("getConstantEncoder", "solanaCodecsDataStructures")}(${constantBytes})`,
1237
1249
  decoder: fragment`${use("getConstantDecoder", "solanaCodecsDataStructures")}(${constantBytes})`
1238
1250
  };
1239
1251
  }
1240
- function getConstantBytesExpression(node) {
1241
- const valueNode = node.value;
1242
- const typeNode = node.type;
1243
- if (valueNode?.kind !== "numberValueNode") {
1252
+ function getConstantBytesExpression(node, self) {
1253
+ if (node.value.kind !== "numberValueNode") {
1244
1254
  throw new Error(
1245
- `Unsupported hidden affix constant value kind: ${valueNode?.kind}`
1255
+ `Unsupported hidden affix constant value kind: ${node.value.kind}`
1246
1256
  );
1247
1257
  }
1248
- const value = Number(valueNode.number);
1249
- if (typeNode?.kind === "numberTypeNode") {
1250
- return fragment`${use(`get${getNumberCodecName(typeNode.format)}Encoder`, "solanaCodecsNumbers")}().encode(${value})`;
1258
+ const manifest = visitorsCore.visit(node.type, self);
1259
+ if (!["int", "BigInt", "double"].includes(manifest.type.content)) {
1260
+ throw new Error(`Unsupported hidden affix constant type kind: ${node.type.kind}`);
1251
1261
  }
1252
- if (typeNode?.kind === "preOffsetTypeNode") {
1253
- const inner = typeNode.type;
1254
- if (inner?.kind !== "numberTypeNode") {
1255
- throw new Error(
1256
- `Unsupported hidden affix pre-offset inner type kind: ${inner?.kind}`
1257
- );
1258
- }
1259
- return fragment`${use("padLeftEncoder", "solanaCodecsCore")}(${use(`get${getNumberCodecName(inner.format)}Encoder`, "solanaCodecsNumbers")}(), ${typeNode.offset}).encode(${value})`;
1262
+ const value = getDartValueFragment(node.value, manifest.type.content);
1263
+ return fragment`${manifest.encoder}.encode(${value})`;
1264
+ }
1265
+ function getOffsetManifest(node, self) {
1266
+ if (!Number.isSafeInteger(node.offset)) {
1267
+ throw new Error("Expected a safe integer for a codec offset");
1260
1268
  }
1261
- throw new Error(
1262
- `Unsupported hidden affix constant type kind: ${typeNode?.kind}`
1263
- );
1269
+ const manifest = visitorsCore.visit(node.type, self);
1270
+ const pre = node.kind === "preOffsetTypeNode";
1271
+ const key = pre ? "preOffset" : "postOffset";
1272
+ if (node.strategy === "padded") {
1273
+ const side = pre ? "Left" : "Right";
1274
+ const offset = getNonNegativeInteger(node.offset);
1275
+ return {
1276
+ ...manifest,
1277
+ encoder: fragment`${use(`pad${side}Encoder`, "solanaCodecsCore")}(${manifest.encoder}, ${offset})`,
1278
+ decoder: fragment`${use(`pad${side}Decoder`, "solanaCodecsCore")}(${manifest.decoder}, ${offset})`
1279
+ };
1280
+ }
1281
+ let expression;
1282
+ switch (node.strategy) {
1283
+ case "absolute":
1284
+ expression = node.offset < 0 ? `scope.wrapBytes(${node.offset})` : String(node.offset);
1285
+ break;
1286
+ case "relative":
1287
+ expression = `scope.${key} + ${node.offset}`;
1288
+ break;
1289
+ case "preOffset":
1290
+ expression = `scope.preOffset + ${node.offset}`;
1291
+ break;
1292
+ default:
1293
+ throw new Error(`Unsupported offset strategy: ${node.strategy}`);
1294
+ }
1295
+ const config = fragment`${use("OffsetConfig", "solanaCodecsCore")}(${key}: (scope) => ${expression})`;
1296
+ return {
1297
+ ...manifest,
1298
+ encoder: fragment`${use("offsetEncoder", "solanaCodecsCore")}(${manifest.encoder}, ${config})`,
1299
+ decoder: fragment`${use("offsetDecoder", "solanaCodecsCore")}(${manifest.decoder}, ${config})`
1300
+ };
1264
1301
  }
1265
1302
 
1266
1303
  // src/fragments/discriminatorConstants.ts
@@ -1393,7 +1430,7 @@ ${ctorParams}
1393
1430
  const decodeFnName = scope.nameApi.accountDecodeFunction(name);
1394
1431
  const sizeFragment = node.size != null ? fragment`
1395
1432
  /// The size of the [${fragmentFromString(typeName)}] account data in bytes.
1396
- const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(String(node.size))};` : emptyFragment();
1433
+ const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(getNonNegativeInteger(node.size))};` : emptyFragment();
1397
1434
  const discFragment = getDiscriminatorConstantsFragment(node, scope);
1398
1435
  const discriminatorValidation = getDiscriminatorValidationFragment(
1399
1436
  node,
@@ -1507,9 +1544,10 @@ function getErrorPageFragment(programNode, scope) {
1507
1544
  programName,
1508
1545
  err.name
1509
1546
  );
1547
+ getNonNegativeInteger(err.code);
1510
1548
  const hexCode = `0x${err.code.toString(16)}`;
1511
- const docs = err.docs?.length ? err.docs.map((d) => `/// ${d}`).join("\n") + "\n" : "";
1512
- const message = err.message ? `/// Message: "${err.message}"
1549
+ const docs = err.docs?.length ? err.docs.map(toDartDocComment).join("\n") + "\n" : "";
1550
+ const message = err.message ? `${toDartDocComment(`Message: "${err.message}"`)}
1513
1551
  ` : "";
1514
1552
  return `${docs}${message}const int ${constName} = ${hexCode}; // ${err.code}`;
1515
1553
  }).join("\n\n");
@@ -1519,7 +1557,7 @@ function getErrorPageFragment(programNode, scope) {
1519
1557
  err.name
1520
1558
  );
1521
1559
  const message = err.message ?? err.name;
1522
- return ` ${constName}: '${escapeString(message)}',`;
1560
+ return ` ${constName}: ${toDartStringLiteral(message)},`;
1523
1561
  }).join("\n");
1524
1562
  const errorMessageFnName = scope.nameApi.errorMessageFunction(programName);
1525
1563
  const isErrorFnName = `is${pascalCase(programName)}Error`;
@@ -1545,8 +1583,8 @@ bool ${fragmentFromString(isErrorFnName)}(int code) {
1545
1583
  return _${fragmentFromString(camelCase(programName))}ErrorMessages.containsKey(code);
1546
1584
  }`;
1547
1585
  }
1548
- function escapeString(str) {
1549
- return str.replace(/'/g, "\\'").replace(/\n/g, "\\n");
1586
+ function toDartDocComment(value) {
1587
+ return value.split(/\r\n?|\n/).map((line) => `/// ${line}`).join("\n");
1550
1588
  }
1551
1589
 
1552
1590
  // src/fragments/indexPage.ts
@@ -1556,7 +1594,7 @@ function getIndexPageFragment(fileNames, relativePath = "") {
1556
1594
  "// Auto-generated. Do not edit.\n// ignore_for_file: type=lint\n"
1557
1595
  );
1558
1596
  }
1559
- const exports = fileNames.sort().map((f) => {
1597
+ const exports$1 = fileNames.sort().map((f) => {
1560
1598
  const path = relativePath ? `${relativePath}/${f}` : f;
1561
1599
  return `export '${path}';`;
1562
1600
  }).join("\n");
@@ -1564,17 +1602,17 @@ function getIndexPageFragment(fileNames, relativePath = "") {
1564
1602
  `// Auto-generated. Do not edit.
1565
1603
  // ignore_for_file: type=lint
1566
1604
 
1567
- ${exports}
1605
+ ${exports$1}
1568
1606
  `
1569
1607
  );
1570
1608
  }
1571
1609
  function getRootIndexPageFragment(categories) {
1572
- const exports = categories.sort().map((cat) => `export '${cat}/${cat}.dart';`).join("\n");
1610
+ const exports$1 = categories.sort().map((cat) => `export '${cat}/${cat}.dart';`).join("\n");
1573
1611
  return fragmentFromString(
1574
1612
  `// Auto-generated. Do not edit.
1575
1613
  // ignore_for_file: type=lint
1576
1614
 
1577
- ${exports}
1615
+ ${exports$1}
1578
1616
  `
1579
1617
  );
1580
1618
  }
@@ -1672,14 +1710,28 @@ ${dataCtorInitializers}` : ""};`;
1672
1710
  const dataEncoderName = `get${typeName}InstructionDataEncoder`;
1673
1711
  const dataDecoderName = `get${typeName}InstructionDataDecoder`;
1674
1712
  const dataCodecName = `get${typeName}InstructionDataCodec`;
1675
- const argNames = new Set(args.map((a) => camelCase(a.name)));
1676
- const accountNames = new Set(accounts.map((a) => camelCase(a.name)));
1677
- const hasProgramAddressCollision = argNames.has("programAddress") || accountNames.has("programAddress");
1678
- const instrProgramParam = hasProgramAddressCollision ? "instructionProgramAddress" : "programAddress";
1679
- let instructionDataLocal = "instructionData";
1680
- while (argNames.has(instructionDataLocal) || accountNames.has(instructionDataLocal)) {
1681
- instructionDataLocal = `${instructionDataLocal}_`;
1713
+ const usedNames = /* @__PURE__ */ new Set([
1714
+ ...args.map((arg) => camelCase(arg.name)),
1715
+ ...accounts.map((account) => camelCase(account.name))
1716
+ ]);
1717
+ const instrProgramParam = reserveName(
1718
+ usedNames.has("programAddress") ? "instructionProgramAddress" : "programAddress",
1719
+ usedNames
1720
+ );
1721
+ const instructionDataLocal = reserveName("instructionData", usedNames);
1722
+ const signerParams = /* @__PURE__ */ new Map();
1723
+ for (const account of accounts) {
1724
+ if (account.isSigner === "either") {
1725
+ signerParams.set(
1726
+ account,
1727
+ reserveName(`${camelCase(account.name)}IsSigner`, usedNames)
1728
+ );
1729
+ }
1682
1730
  }
1731
+ const signerParamDeclarations = [...signerParams.values()].map((parameter) => ` bool ${parameter} = true,`).join("\n");
1732
+ const signerParamDocs = [...signerParams.entries()].map(
1733
+ ([account, parameter]) => `/// Set [${parameter}] to false when [${camelCase(account.name)}] does not sign (for example, a multisig authority).`
1734
+ ).join("\n");
1683
1735
  const accountParams = accounts.map((acc) => {
1684
1736
  const fieldName = camelCase(acc.name);
1685
1737
  const isRequired = !(acc.isOptional ?? false);
@@ -1699,7 +1751,7 @@ ${dataCtorInitializers}` : ""};`;
1699
1751
  }).join("\n");
1700
1752
  const accountMetas = accounts.map((acc) => {
1701
1753
  const fieldName = camelCase(acc.name);
1702
- const role = getAccountRole(acc);
1754
+ const role = getAccountRole(acc, signerParams.get(acc));
1703
1755
  const isOptional = acc.isOptional ?? false;
1704
1756
  if (isOptional) {
1705
1757
  if (optionalAccountStrategy === "omitted") {
@@ -1782,11 +1834,13 @@ Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}
1782
1834
  return combineCodec(${fragmentFromString(dataEncoderName)}(), ${fragmentFromString(dataDecoderName)}());
1783
1835
  }`);
1784
1836
  parts.push(fragment`
1785
- /// Creates a [${fragmentFromString(typeName)}] instruction.
1837
+ /// Creates a [${fragmentFromString(typeName)}] instruction.${fragmentFromString(signerParamDocs ? `
1838
+ ${signerParamDocs}` : "")}
1786
1839
  Instruction ${fragmentFromString(instrFnName)}({
1787
1840
  required Address ${fragmentFromString(instrProgramParam)},
1788
1841
  ${fragmentFromString(accountParams)}
1789
- ${fragmentFromString(argParams)}
1842
+ ${fragmentFromString(argParams)}${fragmentFromString(signerParamDeclarations ? `
1843
+ ${signerParamDeclarations}` : "")}
1790
1844
  }) {
1791
1845
  final ${instructionDataLocal} = ${fragmentFromString(dataClassName)}(
1792
1846
  ${fragmentFromString(dataConstruction)}
@@ -1816,13 +1870,20 @@ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruct
1816
1870
  }
1817
1871
  return result;
1818
1872
  }
1819
- function getAccountRole(acc) {
1820
- const isSigner = acc.isSigner === true || acc.isSigner === "either";
1821
- const isWritable = acc.isWritable ?? false;
1822
- if (isSigner && isWritable) return "AccountRole.writableSigner";
1823
- if (isSigner) return "AccountRole.readonlySigner";
1824
- if (isWritable) return "AccountRole.writable";
1825
- return "AccountRole.readonly";
1873
+ function reserveName(preferredName, usedNames) {
1874
+ let name = preferredName;
1875
+ while (usedNames.has(name)) {
1876
+ name = `${name}_`;
1877
+ }
1878
+ usedNames.add(name);
1879
+ return name;
1880
+ }
1881
+ function getAccountRole(acc, signerParameter) {
1882
+ const role = acc.isWritable ? "AccountRole.writable" : "AccountRole.readonly";
1883
+ if (signerParameter) {
1884
+ return `${signerParameter} ? ${role}Signer : ${role}`;
1885
+ }
1886
+ return acc.isSigner === true ? `${role}Signer` : role;
1826
1887
  }
1827
1888
  function isDiscriminatorArg(arg, node) {
1828
1889
  const discriminators = node.discriminators ?? [];
@@ -1875,9 +1936,9 @@ function getPdaPageFragment(node, scope) {
1875
1936
  for (const seed of seeds) {
1876
1937
  if (seed.kind === "constantPdaSeedNode") {
1877
1938
  if (seed.value.kind === "bytesValueNode") {
1878
- seedValues.push(` Uint8List.fromList(${bytesValueToDart(seed.value.data)}),`);
1939
+ seedValues.push(` ${getDartValueFragment(seed.value).content},`);
1879
1940
  } else if (seed.value.kind === "stringValueNode") {
1880
- seedValues.push(` '${seed.value.string}',`);
1941
+ seedValues.push(` ${toDartStringLiteral(seed.value.string)},`);
1881
1942
  } else if (seed.value.kind === "publicKeyValueNode") {
1882
1943
  const wellKnownSeedName = WELL_KNOWN_ADDRESSES.get(seed.value.publicKey);
1883
1944
  if (wellKnownSeedName) {
@@ -1887,13 +1948,14 @@ function getPdaPageFragment(node, scope) {
1887
1948
  );
1888
1949
  } else {
1889
1950
  seedValues.push(
1890
- ` getAddressEncoder().encode(Address('${seed.value.publicKey}')),`
1951
+ ` getAddressEncoder().encode(Address(${toDartStringLiteral(seed.value.publicKey)})),`
1891
1952
  );
1892
1953
  }
1893
1954
  } else if (seed.value.kind === "numberValueNode") {
1894
1955
  const manifest = visitorsCore.visit(seed.type, scope.typeManifestVisitor);
1895
1956
  seedManifests.push(manifest);
1896
- seedValues.push(` ${manifest.encoder.content}.encode(${seed.value.number}),`);
1957
+ const value = getDartValueFragment(seed.value, manifest.type.content);
1958
+ seedValues.push(` ${manifest.encoder.content}.encode(${value.content}),`);
1897
1959
  }
1898
1960
  } else if (seed.kind === "variablePdaSeedNode") {
1899
1961
  const manifest = visitorsCore.visit(seed.type, scope.typeManifestVisitor);
@@ -1907,7 +1969,7 @@ function getPdaPageFragment(node, scope) {
1907
1969
  if (wellKnownProgramName) {
1908
1970
  use(wellKnownProgramName, "solanaAddresses");
1909
1971
  }
1910
- const programIdParam = node.programId ? wellKnownProgramName ? `Address programAddress = ${wellKnownProgramName}` : `Address programAddress = const Address('${node.programId}')` : "required Address programAddress";
1972
+ const programIdParam = node.programId ? wellKnownProgramName ? `Address programAddress = ${wellKnownProgramName}` : `Address programAddress = const Address(${toDartStringLiteral(node.programId)})` : "required Address programAddress";
1911
1973
  const typedDataImport = hasByteSeeds ? use("Uint8List", "dartTypedData") : fragmentFromString("");
1912
1974
  const metaImport = hasSeedsClass ? use("immutable", "meta") : fragmentFromString("");
1913
1975
  const parts = [
@@ -1954,14 +2016,6 @@ ${fragmentFromString(seedValues.join("\n"))}
1954
2016
  }
1955
2017
  return result;
1956
2018
  }
1957
- function bytesValueToDart(hex) {
1958
- const clean = hex.replace(/^0x/, "");
1959
- const pairs = [];
1960
- for (let i = 0; i < clean.length; i += 2) {
1961
- pairs.push(`0x${clean.slice(i, i + 2)}`);
1962
- }
1963
- return `[${pairs.join(", ")}]`;
1964
- }
1965
2019
  function getProgramPageFragment(node, scope) {
1966
2020
  const name = node.name;
1967
2021
  const addressConstName = scope.nameApi.programAddressConstant(name);
@@ -1982,7 +2036,7 @@ const ${fragmentFromString(addressConstName)} = ${fragmentFromString(wellKnownNa
1982
2036
  ${use("Address", "solanaAddresses")}
1983
2037
 
1984
2038
  /// The address of the ${fragmentFromString(pascalCase(name))} program.
1985
- const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}');`;
2039
+ const ${fragmentFromString(addressConstName)} = Address(${fragmentFromString(toDartStringLiteral(node.publicKey))});`;
1986
2040
  const parts = [addressDeclaration];
1987
2041
  const accounts = node.accounts ?? [];
1988
2042
  if (accounts.length > 0) {
@@ -2123,7 +2177,7 @@ function getValueFragment(value, dartType) {
2123
2177
  case "booleanValueNode":
2124
2178
  return fragmentFromString(String(value.boolean));
2125
2179
  case "stringValueNode":
2126
- return fragmentFromString(`'${value.string.replaceAll("'", "\\'")}'`);
2180
+ return fragmentFromString(toDartStringLiteral(value.string));
2127
2181
  case "bytesValueNode": {
2128
2182
  const clean = value.data.replace(/^0x/, "");
2129
2183
  const bytes = clean.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) ?? [];
@@ -2689,9 +2743,12 @@ function getRenderMapVisitor(options = {}) {
2689
2743
  visitorsCore.visit(node.program, self),
2690
2744
  ...(node.additionalPrograms ?? []).map((p) => visitorsCore.visit(p, self))
2691
2745
  ];
2692
- const mergedProgramMap = renderersCore.mergeRenderMaps(programMaps);
2746
+ const mergedProgramMap = mergeRenderMapsWithoutCollisions(programMaps);
2693
2747
  const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
2694
- return renderersCore.mergeRenderMaps([mergedProgramMap, ...categoryMaps]);
2748
+ return mergeRenderMapsWithoutCollisions([
2749
+ mergedProgramMap,
2750
+ ...categoryMaps
2751
+ ]);
2695
2752
  },
2696
2753
  visitProgram(node, { self }) {
2697
2754
  const programName = node.name;
@@ -2753,7 +2810,7 @@ function getRenderMapVisitor(options = {}) {
2753
2810
  );
2754
2811
  maps.push(rootMap);
2755
2812
  }
2756
- return renderersCore.mergeRenderMaps(maps);
2813
+ return mergeRenderMapsWithoutCollisions(maps);
2757
2814
  },
2758
2815
  visitAccount(node, { self }) {
2759
2816
  const fileName = `${snakeCase(node.name)}.dart`;
@@ -2788,6 +2845,18 @@ function getRenderMapVisitor(options = {}) {
2788
2845
  (v) => visitorsCore.recordLinkablesOnFirstVisitVisitor(v, linkables)
2789
2846
  );
2790
2847
  }
2848
+ function mergeRenderMapsWithoutCollisions(maps) {
2849
+ const paths = /* @__PURE__ */ new Set();
2850
+ for (const map of maps) {
2851
+ for (const path of map.keys()) {
2852
+ if (paths.has(path)) {
2853
+ throw new Error(`Duplicate generated path "${path}"`);
2854
+ }
2855
+ paths.add(path);
2856
+ }
2857
+ }
2858
+ return renderersCore.mergeRenderMaps(maps);
2859
+ }
2791
2860
  function getSharedCategoryIndexMaps(renderMap) {
2792
2861
  const categories = /* @__PURE__ */ new Map();
2793
2862
  const categoryIndexNames = {
@@ -2804,7 +2873,6 @@ function getSharedCategoryIndexMaps(renderMap) {
2804
2873
  );
2805
2874
  if (!match) continue;
2806
2875
  const [, category, fileName] = match;
2807
- if (fileName === categoryIndexNames[category]) continue;
2808
2876
  if (!categories.has(category)) {
2809
2877
  categories.set(category, /* @__PURE__ */ new Set());
2810
2878
  }
@@ -2834,9 +2902,6 @@ function renderVisitor(outputDir, options = {}) {
2834
2902
  } = options;
2835
2903
  return visitorsCore.rootNodeVisitor((root) => {
2836
2904
  const normalizedRoot = normalizeRootNode(root);
2837
- if (deleteFolderBeforeRendering && fs.existsSync(outputDir)) {
2838
- renderersCore.deleteDirectory(outputDir);
2839
- }
2840
2905
  const renderMap = visitorsCore.visit(
2841
2906
  normalizedRoot,
2842
2907
  getRenderMapVisitor({ nameApi, dependencyMap })
@@ -2848,12 +2913,9 @@ function renderVisitor(outputDir, options = {}) {
2848
2913
  typePathMap[`definedType:${match[1]}`] = renderPath;
2849
2914
  }
2850
2915
  }
2916
+ const files = [];
2851
2917
  for (const [filePath, frag] of renderMap.entries()) {
2852
2918
  const fullPath = path.join(outputDir, filePath);
2853
- const dir = path.dirname(fullPath);
2854
- if (!fs.existsSync(dir)) {
2855
- fs.mkdirSync(dir, { recursive: true });
2856
- }
2857
2919
  const fileDir = path.dirname(filePath);
2858
2920
  const internalMap = {};
2859
2921
  for (const [key, typePath] of Object.entries(typePathMap)) {
@@ -2868,6 +2930,13 @@ function renderVisitor(outputDir, options = {}) {
2868
2930
  internalMap[key] = rel;
2869
2931
  }
2870
2932
  const content = resolveFragmentContent(frag, dependencyMap ?? {}, internalMap);
2933
+ files.push([fullPath, content]);
2934
+ }
2935
+ if (deleteFolderBeforeRendering && fs.existsSync(outputDir)) {
2936
+ renderersCore.deleteDirectory(outputDir);
2937
+ }
2938
+ for (const [fullPath, content] of files) {
2939
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
2871
2940
  fs.writeFileSync(fullPath, content, "utf-8");
2872
2941
  }
2873
2942
  if (formatCode) {