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.
- package/README.md +6 -0
- package/dist/index.browser.cjs +176 -107
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +177 -108
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +176 -107
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +177 -108
- package/dist/index.node.js.map +1 -1
- package/dist/index.react-native.js +177 -108
- package/dist/index.react-native.js.map +1 -1
- package/dist/types/fragments/accountPage.d.ts.map +1 -1
- package/dist/types/fragments/errorPage.d.ts.map +1 -1
- package/dist/types/fragments/instructionPage.d.ts.map +1 -1
- package/dist/types/fragments/pdaPage.d.ts.map +1 -1
- package/dist/types/fragments/programPage.d.ts.map +1 -1
- package/dist/types/utils/nameTransformers.d.ts.map +1 -1
- package/dist/types/utils/valueNodes.d.ts +4 -0
- package/dist/types/utils/valueNodes.d.ts.map +1 -1
- package/dist/types/visitors/getRenderMapVisitor.d.ts.map +1 -1
- package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
- package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/fragments/accountPage.ts +2 -1
- package/src/fragments/errorPage.ts +8 -5
- package/src/fragments/instructionPage.ts +51 -21
- package/src/fragments/pdaPage.ts +7 -14
- package/src/fragments/programPage.ts +3 -2
- package/src/utils/formatCode.ts +2 -2
- package/src/utils/nameTransformers.ts +11 -0
- package/src/utils/valueNodes.ts +24 -1
- package/src/visitors/getRenderMapVisitor.ts +30 -5
- package/src/visitors/getTypeManifestVisitor.ts +100 -75
- package/src/visitors/renderVisitor.ts +11 -13
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ A [Codama](https://github.com/codama-idl/codama) renderer that generates Dart co
|
|
|
6
6
|
|
|
7
7
|
Given a Codama IDL (Interface Description Language) describing a Solana program, this renderer produces a complete Dart package with typed account classes, instruction builders, codec functions, error definitions, PDA helpers, and barrel exports.
|
|
8
8
|
|
|
9
|
+
Numeric prefix codecs retain the declared byte order, and pre/post offset types retain their cursor or padding strategy. Constant PDA integer seeds use the matching Dart integer type; malformed hexadecimal seeds and unsupported byte encodings are rejected.
|
|
10
|
+
|
|
9
11
|
Fixed-size Codama types generate non-truncating encoders. Values within the declared byte capacity are zero-padded, while over-capacity values throw before any malformed bytes can be emitted. For UTF-8 strings, capacity is measured in encoded bytes rather than Dart code units.
|
|
10
12
|
|
|
11
13
|
## Installation
|
|
@@ -39,6 +41,8 @@ visit(root, renderVisitor("lib/src/generated", {
|
|
|
39
41
|
|
|
40
42
|
`renderVisitor` accepts parsed Codama JSON as well as roots made with Codama constructors. Pina omits empty collections from its serialized IDLs; the renderer restores those structural defaults on an internal copy before traversal, leaving the parsed object unchanged.
|
|
41
43
|
|
|
44
|
+
IDL names may contain letters, digits, underscores, hyphens, and spaces; names containing path separators, quotes, or other source syntax are rejected. Documentation and string values are escaped before they enter generated Dart. Rendering resolves all output before clearing the previous generated directory, so invalid IDLs do not delete an existing client. Formatter paths are passed as literal process arguments. Numeric defaults, counts, sizes, and error codes are validated before they are written as source. Duplicate generated paths are rejected, including names reserved for category barrel files, so nodes cannot silently replace one another.
|
|
45
|
+
|
|
42
46
|
```ts
|
|
43
47
|
import { readFile } from "node:fs/promises";
|
|
44
48
|
import { visit } from "@codama/visitors-core";
|
|
@@ -160,6 +164,8 @@ Account and instruction decoders validate every declared constant, field, and si
|
|
|
160
164
|
|
|
161
165
|
Top-level instruction decoders require exact input consumption. Account decoders always reject truncated data, but accept unread trailing capacity unless the account declares a `sizeDiscriminatorNode`; size-discriminated accounts reject both truncation and suffix bytes.
|
|
162
166
|
|
|
167
|
+
Accounts declared with `isSigner: "either"` expose an additional boolean such as `authorityIsSigner`, defaulting to `true`. Set it to `false` when the authority does not sign, including multisig authorities.
|
|
168
|
+
|
|
163
169
|
Optional instruction accounts use Codama's `programId` strategy by default. When an optional account is absent, the builder emits a readonly program-address placeholder so every later account keeps its declared index. The account slot is removed only when the instruction explicitly selects the legacy `omitted` strategy.
|
|
164
170
|
|
|
165
171
|
### Scalar enums
|
package/dist/index.browser.cjs
CHANGED
|
@@ -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.
|
|
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
|
|
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(
|
|
657
|
-
encoder:
|
|
658
|
-
|
|
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
|
};
|
|
@@ -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) => `(
|
|
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) => `(
|
|
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
|
|
908
|
-
const prefixDecoderExpr = resolvedPrefix.format !== "u8" ? `, prefix: ${getNumberCodecExpression(resolvedPrefix
|
|
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)${
|
|
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}${
|
|
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(
|
|
979
|
+
)}(${innerManifest.encoder}, ${fragmentFromString(getNonNegativeInteger(node.size))}, allowTruncation: false)`,
|
|
969
980
|
decoder: fragment`${use(
|
|
970
981
|
"fixDecoderSize",
|
|
971
982
|
"solanaCodecsCore"
|
|
972
|
-
)}(${innerManifest.decoder}, ${fragmentFromString(
|
|
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
|
|
993
|
+
const numberManifest = visitorsCore.visit(node.prefix, self);
|
|
983
994
|
prefixManifest = {
|
|
984
|
-
encoder: fragment
|
|
985
|
-
decoder: fragment
|
|
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
|
|
1087
|
+
return getOffsetManifest(node, self);
|
|
1077
1088
|
},
|
|
1078
1089
|
visitPostOffsetType(node, { self }) {
|
|
1079
|
-
return
|
|
1090
|
+
return getOffsetManifest(node, self);
|
|
1080
1091
|
},
|
|
1081
1092
|
visitSolAmountType(node, { self }) {
|
|
1082
1093
|
return visitorsCore.visit(node.number, self);
|
|
@@ -1165,16 +1176,17 @@ function getNumberCodecName(format) {
|
|
|
1165
1176
|
return "U8";
|
|
1166
1177
|
}
|
|
1167
1178
|
}
|
|
1168
|
-
function getNumberCodecExpression(
|
|
1169
|
-
const name = getNumberCodecName(format);
|
|
1170
|
-
|
|
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
1184
|
function getArraySizeExpression(node, codecType) {
|
|
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": {
|
|
@@ -1182,7 +1194,7 @@ function getArraySizeExpression(node, codecType) {
|
|
|
1182
1194
|
if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
|
|
1183
1195
|
return "";
|
|
1184
1196
|
}
|
|
1185
|
-
return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix
|
|
1197
|
+
return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
|
|
1186
1198
|
}
|
|
1187
1199
|
default:
|
|
1188
1200
|
return "";
|
|
@@ -1193,7 +1205,7 @@ function getMapSizeExpression(node, codecType) {
|
|
|
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": {
|
|
@@ -1201,7 +1213,7 @@ function getMapSizeExpression(node, codecType) {
|
|
|
1201
1213
|
if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
|
|
1202
1214
|
return "";
|
|
1203
1215
|
}
|
|
1204
|
-
return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix
|
|
1216
|
+
return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
|
|
1205
1217
|
}
|
|
1206
1218
|
default:
|
|
1207
1219
|
return "";
|
|
@@ -1212,7 +1224,7 @@ function getSetSizeExpression(node, codecType) {
|
|
|
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": {
|
|
@@ -1220,7 +1232,7 @@ function getSetSizeExpression(node, codecType) {
|
|
|
1220
1232
|
if (resolvedPrefix.format === "u32" && (!resolvedPrefix.endian || resolvedPrefix.endian === "le")) {
|
|
1221
1233
|
return "";
|
|
1222
1234
|
}
|
|
1223
|
-
return `, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix
|
|
1235
|
+
return fragment`, size: PrefixedArraySize(${getNumberCodecExpression(resolvedPrefix, codecType)})`;
|
|
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
|
-
|
|
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: ${
|
|
1255
|
+
`Unsupported hidden affix constant value kind: ${node.value.kind}`
|
|
1246
1256
|
);
|
|
1247
1257
|
}
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1250
|
-
|
|
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
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
-
|
|
1262
|
-
|
|
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(
|
|
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(
|
|
1512
|
-
const 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}:
|
|
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
|
|
1549
|
-
return
|
|
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
|
|
@@ -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
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
return
|
|
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(`
|
|
1939
|
+
seedValues.push(` ${getDartValueFragment(seed.value).content},`);
|
|
1879
1940
|
} else if (seed.value.kind === "stringValueNode") {
|
|
1880
|
-
seedValues.push(`
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
2746
|
+
const mergedProgramMap = mergeRenderMapsWithoutCollisions(programMaps);
|
|
2693
2747
|
const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
|
|
2694
|
-
return
|
|
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
|
|
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) {
|