codama-renderers-dart 0.5.1 → 0.5.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"instructionPage.d.ts","sourceRoot":"","sources":["../../../src/fragments/instructionPage.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAUvD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,WAAW,GACjB,QAAQ,CAuUV"}
1
+ {"version":3,"file":"instructionPage.d.ts","sourceRoot":"","sources":["../../../src/fragments/instructionPage.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAUvD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,WAAW,GACjB,QAAQ,CA8UV"}
@@ -4,6 +4,11 @@ import type { CamelCaseString } from "@codama/nodes";
4
4
  */
5
5
  /** Convert a string to PascalCase */
6
6
  export declare function pascalCase(str: string): string;
7
+ /**
8
+ * Escape a lowercase identifier if it collides with a Dart reserved word
9
+ * by appending `_` (e.g. an account named `null` renders as `null_`).
10
+ */
11
+ export declare function escapeDartIdentifier(name: string): string;
7
12
  /** Convert a string to camelCase */
8
13
  export declare function camelCase(str: string): string;
9
14
  /** Convert a string to snake_case */
@@ -1 +1 @@
1
- {"version":3,"file":"nameTransformers.d.ts","sourceRoot":"","sources":["../../../src/utils/nameTransformers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;GAEG;AAEH,qCAAqC;AACrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,oCAAoC;AACpC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,sDAAsD;IACtD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,sDAAsD;IACtD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,kDAAkD;IAClD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,kDAAkD;IAClD,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,6DAA6D;IAC7D,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChD,oDAAoD;IACpD,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,+CAA+C;IAC/C,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,iDAAiD;IACjD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,iEAAiE;IACjE,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,iEAAiE;IACjE,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/C,4DAA4D;IAC5D,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,4DAA4D;IAC5D,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9D,4DAA4D;IAC5D,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,gCAAgC;IAChC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,mEAAmE;IACnE,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IACpE,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,yDAAyD;IACzD,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,oDAAoD;IACpD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAwB/C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAEhE"}
1
+ {"version":3,"file":"nameTransformers.d.ts","sourceRoot":"","sources":["../../../src/utils/nameTransformers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;GAEG;AAEH,qCAAqC;AACrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAoBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,oCAAoC;AACpC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,sDAAsD;IACtD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,sDAAsD;IACtD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,kDAAkD;IAClD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,kDAAkD;IAClD,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,6DAA6D;IAC7D,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAChD,oDAAoD;IACpD,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,+CAA+C;IAC/C,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,iDAAiD;IACjD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,iEAAiE;IACjE,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,iEAAiE;IACjE,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/C,4DAA4D;IAC5D,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,4DAA4D;IAC5D,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9D,4DAA4D;IAC5D,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3C,gCAAgC;IAChC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,mEAAmE;IACnE,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IACpE,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,yDAAyD;IACzD,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,oDAAoD;IACpD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAwB/C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAEhE"}
@@ -15,5 +15,5 @@ export declare function getTypeManifestVisitor(input: {
15
15
  linkables: LinkableDictionary;
16
16
  stack: NodeStack;
17
17
  nonScalarEnums?: CamelCaseString[];
18
- }): Visitor<TypeManifest, "accountBumpValueNode" | "accountValueNode" | "argumentValueNode" | "conditionalValueNode" | "identityValueNode" | "payerValueNode" | "pdaValueNode" | "programIdValueNode" | "resolverValueNode" | "programLinkNode" | "arrayValueNode" | "booleanValueNode" | "bytesValueNode" | "constantValueNode" | "enumValueNode" | "mapValueNode" | "noneValueNode" | "numberValueNode" | "publicKeyValueNode" | "setValueNode" | "someValueNode" | "stringValueNode" | "structValueNode" | "tupleValueNode" | "amountTypeNode" | "arrayTypeNode" | "booleanTypeNode" | "bytesTypeNode" | "dateTimeTypeNode" | "enumTypeNode" | "fixedSizeTypeNode" | "hiddenPrefixTypeNode" | "hiddenSuffixTypeNode" | "mapTypeNode" | "numberTypeNode" | "optionTypeNode" | "postOffsetTypeNode" | "preOffsetTypeNode" | "publicKeyTypeNode" | "remainderOptionTypeNode" | "sentinelTypeNode" | "setTypeNode" | "sizePrefixTypeNode" | "solAmountTypeNode" | "stringTypeNode" | "structTypeNode" | "tupleTypeNode" | "zeroableOptionTypeNode" | "constantDiscriminatorNode" | "fieldDiscriminatorNode" | "sizeDiscriminatorNode" | "accountNode" | "constantNode" | "definedTypeNode" | "errorNode" | "eventNode" | "instructionAccountNode" | "instructionArgumentNode" | "instructionByteDeltaNode" | "instructionNode" | "instructionRemainingAccountsNode" | "instructionStatusNode" | "pdaNode" | "programNode" | "pdaSeedValueNode" | "fixedCountNode" | "prefixedCountNode" | "remainderCountNode" | "accountLinkNode" | "definedTypeLinkNode" | "instructionAccountLinkNode" | "instructionArgumentLinkNode" | "instructionLinkNode" | "pdaLinkNode" | "constantPdaSeedNode" | "variablePdaSeedNode" | "enumEmptyVariantTypeNode" | "enumStructVariantTypeNode" | "enumTupleVariantTypeNode" | "structFieldTypeNode" | "mapEntryValueNode" | "structFieldValueNode" | "rootNode">;
18
+ }): Visitor<TypeManifest, "accountBumpValueNode" | "accountFieldValueNode" | "accountValueNode" | "argumentValueNode" | "conditionalValueNode" | "identityValueNode" | "payerValueNode" | "pdaValueNode" | "programIdValueNode" | "resolverValueNode" | "programLinkNode" | "arrayValueNode" | "booleanValueNode" | "bytesValueNode" | "constantValueNode" | "enumValueNode" | "injectedValueNode" | "mapValueNode" | "noneValueNode" | "numberValueNode" | "publicKeyValueNode" | "setValueNode" | "someValueNode" | "stringValueNode" | "structValueNode" | "tupleValueNode" | "amountTypeNode" | "arrayTypeNode" | "booleanTypeNode" | "bytesTypeNode" | "dateTimeTypeNode" | "enumTypeNode" | "fixedSizeTypeNode" | "hiddenPrefixTypeNode" | "hiddenSuffixTypeNode" | "mapTypeNode" | "numberTypeNode" | "optionTypeNode" | "postOffsetTypeNode" | "preOffsetTypeNode" | "publicKeyTypeNode" | "remainderOptionTypeNode" | "sentinelTypeNode" | "setTypeNode" | "sizePrefixTypeNode" | "solAmountTypeNode" | "stringTypeNode" | "structTypeNode" | "tupleTypeNode" | "zeroableOptionTypeNode" | "structFieldDisplayNode" | "constantDiscriminatorNode" | "fieldDiscriminatorNode" | "sizeDiscriminatorNode" | "instructionAccountDisplayNode" | "instructionStatusNode" | "accountNode" | "constantNode" | "definedTypeNode" | "errorNode" | "eventNode" | "instructionAccountNode" | "instructionArgumentNode" | "instructionByteDeltaNode" | "instructionNode" | "instructionRemainingAccountsNode" | "pdaNode" | "pluginNode" | "programNode" | "providedNode" | "pdaSeedValueNode" | "fixedCountNode" | "prefixedCountNode" | "remainderCountNode" | "amountNumberDisplayNode" | "dateTimeNumberDisplayNode" | "durationNumberDisplayNode" | "enumVariantDisplayNode" | "instructionDisplayNode" | "stringDisplayNode" | "accountLinkNode" | "definedTypeLinkNode" | "instructionAccountLinkNode" | "instructionArgumentLinkNode" | "instructionLinkNode" | "pdaLinkNode" | "constantPdaSeedNode" | "variablePdaSeedNode" | "enumEmptyVariantTypeNode" | "enumStructVariantTypeNode" | "enumTupleVariantTypeNode" | "structFieldTypeNode" | "mapEntryValueNode" | "structFieldValueNode" | "rootNode">;
19
19
  //# sourceMappingURL=getTypeManifestVisitor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getTypeManifestVisitor.d.ts","sourceRoot":"","sources":["../../../src/visitors/getTypeManifestVisitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EA8BrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,OAAO,EAIb,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAEL,KAAK,YAAY,EAKlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,8BAA8B,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,CAAC,EAAE,eAAe,EAAE,CAAC;CACpC,6xDAmnBA"}
1
+ {"version":3,"file":"getTypeManifestVisitor.d.ts","sourceRoot":"","sources":["../../../src/visitors/getTypeManifestVisitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EA8BrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,OAAO,EAIb,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAEL,KAAK,YAAY,EAKlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,8BAA8B,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,CAAC,EAAE,eAAe,EAAE,CAAC;CACpC,8kEAooBA"}
package/package.json CHANGED
@@ -55,7 +55,7 @@
55
55
  "repository": "https://github.com/openbudgetfun/solana_kit/tree/main/packages/codama-renderers-dart",
56
56
  "type": "module",
57
57
  "types": "./dist/types/index.d.ts",
58
- "version": "0.5.1",
58
+ "version": "0.5.3",
59
59
  "scripts": {
60
60
  "build": "pnpm exec rimraf dist && pnpm exec tsup && pnpm exec tsc -p tsconfig.declarations.json",
61
61
  "lint": "eslint --ext .ts src",
@@ -178,6 +178,13 @@ export function getInstructionPageFragment(
178
178
  const hasProgramAddressCollision = argNames.has('programAddress') || accountNames.has('programAddress');
179
179
  const instrProgramParam = hasProgramAddressCollision ? 'instructionProgramAddress' : 'programAddress';
180
180
 
181
+ // The generated local holding the constructed instruction data must not
182
+ // collide with an account or argument parameter name.
183
+ let instructionDataLocal = 'instructionData';
184
+ while (argNames.has(instructionDataLocal) || accountNames.has(instructionDataLocal)) {
185
+ instructionDataLocal = `${instructionDataLocal}_`;
186
+ }
187
+
181
188
  // Build the instruction builder function
182
189
  const accountParams = accounts
183
190
  .map((acc) => {
@@ -322,7 +329,7 @@ Instruction ${fragmentFromString(instrFnName)}({
322
329
  ${fragmentFromString(accountParams)}
323
330
  ${fragmentFromString(argParams)}
324
331
  }) {
325
- final instructionData = ${fragmentFromString(dataClassName)}(
332
+ final ${instructionDataLocal} = ${fragmentFromString(dataClassName)}(
326
333
  ${fragmentFromString(dataConstruction)}
327
334
  );
328
335
 
@@ -331,7 +338,7 @@ ${fragmentFromString(dataConstruction)}
331
338
  accounts: [
332
339
  ${fragmentFromString(accountMetas)}
333
340
  ],
334
- data: ${fragmentFromString(dataEncoderName)}().encode(instructionData),
341
+ data: ${fragmentFromString(dataEncoderName)}().encode(${instructionDataLocal}),
335
342
  );
336
343
  }`);
337
344
 
@@ -147,6 +147,7 @@ function getDataEnumPageFragment(
147
147
  const decoderVariants: string[] = [];
148
148
  const encodeCases: string[] = [];
149
149
  const decodeCases: string[] = [];
150
+ let pageHasListEnums = false;
150
151
  // Collect all field type manifests from variants so we can merge their imports
151
152
  const allVariantManifests: { encoder: Fragment; decoder: Fragment; type: Fragment }[] = [];
152
153
 
@@ -212,12 +213,18 @@ function getDataEnumPageFragment(
212
213
  fields.length === 0
213
214
  ? "true"
214
215
  : fields
215
- .map((f: StructFieldTypeNode) => `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`)
216
+ .map((f: StructFieldTypeNode, index) => {
217
+ const fieldName = camelCase(f.name as string);
218
+ return isListManifest(fieldManifests[index].manifest)
219
+ ? `_listEquals(${fieldName}, other.${fieldName})`
220
+ : `${fieldName} == other.${fieldName}`;
221
+ })
216
222
  .join(" &&\n ");
217
223
 
218
- const hashExpression = getHashExpression(
219
- fields.map((f: StructFieldTypeNode) => camelCase(f.name as string)),
220
- );
224
+ const hashExpression = getValueHashExpression(fields, fieldManifests);
225
+ if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
226
+ pageHasListEnums = true;
227
+ }
221
228
 
222
229
  const toStringFields = fields
223
230
  .map((f: StructFieldTypeNode) => `${camelCase(f.name as string)}: $${camelCase(f.name as string)}`)
@@ -351,7 +358,7 @@ sealed class ${fragmentFromString(typeName)} {
351
358
  const ${fragmentFromString(typeName)}();
352
359
  }
353
360
 
354
- ${fragmentFromString(variantClasses.join("\n\n"))}
361
+ ${fragmentFromString(variantClasses.join("\n\n"))}${pageHasListEnums ? LIST_VALUE_HELPERS : ""}
355
362
 
356
363
  Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
357
364
  return transformEncoder<Map<String, Object?>, ${fragmentFromString(typeName)}>(
@@ -405,6 +412,7 @@ function getStructPageFragment(
405
412
  if (structNode.kind !== "structTypeNode") return emptyFragment();
406
413
 
407
414
  const fields = structNode.fields ?? [];
415
+ let pageHasListStructs = false;
408
416
 
409
417
  // Visit each field type once and collect manifests for reuse
410
418
  const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
@@ -429,19 +437,17 @@ function getStructPageFragment(
429
437
  const eqChecks =
430
438
  fields.length === 0
431
439
  ? "true"
432
- : fields
433
- .map(
434
- (f: StructFieldTypeNode) =>
435
- `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`,
436
- )
440
+ : fieldManifests
441
+ .map(({ field: f, manifest }, index) => {
442
+ const fieldName = camelCase(f.name as string);
443
+ return isListManifest(manifest)
444
+ ? `_listEquals(${fieldName}, other.${fieldName})`
445
+ : `${fieldName} == other.${fieldName}`;
446
+ })
437
447
  .join(" &&\n ");
438
-
439
- const hashFields =
440
- fields.length === 0
441
- ? "runtimeType"
442
- : fields
443
- .map((f: StructFieldTypeNode) => camelCase(f.name as string))
444
- .join(", ");
448
+ if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
449
+ pageHasListStructs = true;
450
+ }
445
451
 
446
452
  const toStringFields = fields
447
453
  .map(
@@ -511,12 +517,14 @@ ${fragmentFromString(fieldDecls)}
511
517
  ${fragmentFromString(eqChecks)};
512
518
 
513
519
  @override
514
- int get hashCode => ${fragmentFromString(getHashExpression(fields.map((f: StructFieldTypeNode) => camelCase(f.name as string))))};
520
+ int get hashCode => ${fragmentFromString(
521
+ getValueHashExpression(fields, fieldManifests),
522
+ )};
515
523
 
516
524
  @override
517
525
  String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
518
526
  }
519
-
527
+ ${pageHasListStructs ? LIST_VALUE_HELPERS : ""}
520
528
  Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
521
529
  final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
522
530
  ${fragmentFromString(encFields)}
@@ -595,6 +603,81 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
595
603
  }`;
596
604
  }
597
605
 
606
+ /**
607
+ * Returns true when the rendered field type is a Dart list or byte sequence,
608
+ * whose reference equality must be replaced with a deep comparison in
609
+ * generated value classes.
610
+ */
611
+ function isListManifest(manifest: { type: Fragment }): boolean {
612
+ return manifest.type.content.startsWith("List<") ||
613
+ manifest.type.content === "Uint8List";
614
+ }
615
+
616
+ /**
617
+ * Private Dart helpers emitted into pages whose value classes contain
618
+ * list-typed fields, so `==` and `hashCode` match recursive structural value
619
+ * equality instead of list reference identity. The recursive branch matters
620
+ * for generated types such as `List<Uint8List>`.
621
+ */
622
+ const LIST_VALUE_HELPERS = `
623
+ bool _listEquals<T>(List<T>? a, List<T>? b) {
624
+ if (identical(a, b)) return true;
625
+ if (a == null || b == null) return a == b;
626
+ if (a.length != b.length) return false;
627
+ for (var i = 0; i < a.length; i++) {
628
+ final left = a[i];
629
+ final right = b[i];
630
+ if (left is List<Object?> && right is List<Object?>) {
631
+ if (!_listEquals(left, right)) return false;
632
+ } else if (left != right) {
633
+ return false;
634
+ }
635
+ }
636
+ return true;
637
+ }
638
+
639
+ Object? _deepHash(Object? value) {
640
+ if (value is List<Object?>) {
641
+ return Object.hashAll(value.map(_deepHash));
642
+ }
643
+ return value;
644
+ }
645
+
646
+ int _listHashCode<T>(List<T>? a) {
647
+ if (a == null) return 0;
648
+ return Object.hashAll(a.map(_deepHash));
649
+ }
650
+ `;
651
+
652
+ /**
653
+ * Builds the `hashCode` expression for a class whose fields render as the
654
+ * given lowercase names. List-typed fields compare structurally through the
655
+ * private `_listHashCode` helper emitted alongside the class.
656
+ */
657
+ function getValueHashExpression(
658
+ fields: StructFieldTypeNode[],
659
+ fieldManifests: { field: StructFieldTypeNode; manifest: { type: Fragment } }[],
660
+ ): string {
661
+ const fieldNames = fields.map((f: StructFieldTypeNode) =>
662
+ camelCase(f.name as string),
663
+ );
664
+ const hasListFields = fieldManifests.some(({ manifest }) =>
665
+ isListManifest(manifest),
666
+ );
667
+ if (!hasListFields) {
668
+ return getHashExpression(fieldNames);
669
+ }
670
+ if (fieldNames.length === 1) {
671
+ return `_listHashCode(${fieldNames[0]})`;
672
+ }
673
+ const hashArgs = fields.map((f: StructFieldTypeNode, index) =>
674
+ isListManifest(fieldManifests[index].manifest)
675
+ ? `_listHashCode(${camelCase(f.name as string)})`
676
+ : camelCase(f.name as string),
677
+ );
678
+ return `Object.hash(${hashArgs.join(", ")})`;
679
+ }
680
+
598
681
  function getHashExpression(fields: string[]): string {
599
682
  if (fields.length === 0) return "runtimeType.hashCode";
600
683
  if (fields.length === 1) return `${fields[0]}.hashCode`;
@@ -11,10 +11,37 @@ export function pascalCase(str: string): string {
11
11
  .replace(/^(.)/, (c) => c.toUpperCase());
12
12
  }
13
13
 
14
+ /**
15
+ * Dart reserved words (plus `async`/`await`/`yield` where they cannot be
16
+ * plain identifiers). A lowercase declaration named with one of these must
17
+ * be escaped, e.g. an account named `null` becomes a `null_` parameter.
18
+ */
19
+ const DART_RESERVED_WORDS = new Set([
20
+ "abstract", "as", "assert", "async", "augment", "await", "base",
21
+ "break", "case", "catch", "class", "const", "continue", "covariant",
22
+ "default", "deferred", "do", "dynamic", "else", "enum", "export",
23
+ "extends", "extension", "external", "factory", "false", "final",
24
+ "finally", "for", "get", "hide", "if", "implements", "import", "in",
25
+ "interface", "is", "late", "library", "mixin", "native", "new", "null",
26
+ "of", "on", "operator", "part", "required", "rethrow", "return",
27
+ "sealed", "set", "show", "static", "super", "switch", "sync", "this",
28
+ "throw", "true", "try", "typedef", "var", "void", "when", "while",
29
+ "with", "yield",
30
+ ]);
31
+
32
+ /**
33
+ * Escape a lowercase identifier if it collides with a Dart reserved word
34
+ * by appending `_` (e.g. an account named `null` renders as `null_`).
35
+ */
36
+ export function escapeDartIdentifier(name: string): string {
37
+ return DART_RESERVED_WORDS.has(name) ? `${name}_` : name;
38
+ }
39
+
14
40
  /** Convert a string to camelCase */
15
41
  export function camelCase(str: string): string {
16
42
  const pascal = pascalCase(str);
17
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
43
+ const cased = pascal.charAt(0).toLowerCase() + pascal.slice(1);
44
+ return escapeDartIdentifier(cased);
18
45
  }
19
46
 
20
47
  /** Convert a string to snake_case */
@@ -239,7 +239,7 @@ export function getTypeManifestVisitor(input: {
239
239
  encoder: fragment`${use(
240
240
  "getArrayEncoder",
241
241
  "solanaCodecsDataStructures",
242
- )}<${itemType}>(${itemEncoder}${encoderSizeExpr})`,
242
+ )}(${itemEncoder}${encoderSizeExpr})`,
243
243
  decoder: fragment`${use(
244
244
  "getArrayDecoder",
245
245
  "solanaCodecsDataStructures",
@@ -305,19 +305,33 @@ export function getTypeManifestVisitor(input: {
305
305
  }
306
306
 
307
307
  const encoderListStr = encoderList.map((e) => e.content).join(", ");
308
+ const decoderListStr = decoderList.map((d) => d.content).join(", ");
309
+ // Two-element tuples render through the typed getTuple2 helpers so the
310
+ // generated value type is the Dart record `(T1, T2)` rather than a
311
+ // heterogeneous List<Object?>. Arity-2 is the only shape that currently
312
+ // appears in supported IDLs; wider tuples stay on the untyped helpers.
313
+ let tupleEncoderFn = "getTupleEncoder";
314
+ let tupleDecoderFn = "getTupleDecoder";
315
+ let tupleEncoderArgs = `[${encoderListStr}]`;
316
+ let tupleDecoderArgs = `[${decoderListStr}]`;
317
+ if (itemManifests.length === 2) {
318
+ tupleEncoderFn = "getTuple2Encoder";
319
+ tupleDecoderFn = "getTuple2Decoder";
320
+ tupleEncoderArgs = encoderListStr;
321
+ tupleDecoderArgs = decoderListStr;
322
+ }
308
323
  const encoderFrag = fragment`${use(
309
- "getTupleEncoder",
324
+ tupleEncoderFn,
310
325
  "solanaCodecsDataStructures",
311
- )}([${fragmentFromString(encoderListStr)}])`;
326
+ )}(${tupleEncoderArgs})`;
312
327
  for (const e of encoderList) {
313
328
  encoderFrag.imports.mergeWith(e.imports);
314
329
  }
315
330
 
316
- const decoderListStr = decoderList.map((d) => d.content).join(", ");
317
331
  const decoderFrag = fragment`${use(
318
- "getTupleDecoder",
332
+ tupleDecoderFn,
319
333
  "solanaCodecsDataStructures",
320
- )}([${fragmentFromString(decoderListStr)}])`;
334
+ )}(${tupleDecoderArgs})`;
321
335
  for (const d of decoderList) {
322
336
  decoderFrag.imports.mergeWith(d.imports);
323
337
  }
@@ -442,7 +456,10 @@ export function getTypeManifestVisitor(input: {
442
456
  encoder: fragment`${use(
443
457
  "getNullableEncoder",
444
458
  "solanaCodecsDataStructures",
445
- )}<${fragmentFromString(itemTypeStr)}>(${itemManifest.encoder}${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
459
+ )}<${fragmentFromString(itemTypeStr)}>(${use(
460
+ "transformEncoder",
461
+ "solanaCodecsCore",
462
+ )}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
446
463
  decoder: fragment`${use(
447
464
  "getNullableDecoder",
448
465
  "solanaCodecsDataStructures",