codama-renderers-dart 0.5.0 → 0.5.1

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 (45) hide show
  1. package/dist/index.browser.cjs +456 -127
  2. package/dist/index.browser.cjs.map +1 -1
  3. package/dist/index.browser.js +456 -128
  4. package/dist/index.browser.js.map +1 -1
  5. package/dist/index.node.cjs +456 -127
  6. package/dist/index.node.cjs.map +1 -1
  7. package/dist/index.node.js +456 -128
  8. package/dist/index.node.js.map +1 -1
  9. package/dist/index.react-native.js +456 -128
  10. package/dist/index.react-native.js.map +1 -1
  11. package/dist/types/fragments/accountPage.d.ts.map +1 -1
  12. package/dist/types/fragments/instructionPage.d.ts.map +1 -1
  13. package/dist/types/fragments/typePage.d.ts.map +1 -1
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/utils/discriminators.d.ts +10 -0
  17. package/dist/types/utils/discriminators.d.ts.map +1 -0
  18. package/dist/types/utils/exactDecoder.d.ts +17 -0
  19. package/dist/types/utils/exactDecoder.d.ts.map +1 -0
  20. package/dist/types/utils/formatCode.d.ts +1 -1
  21. package/dist/types/utils/formatCode.d.ts.map +1 -1
  22. package/dist/types/utils/index.d.ts +4 -0
  23. package/dist/types/utils/index.d.ts.map +1 -1
  24. package/dist/types/utils/normalizeRootNode.d.ts +11 -0
  25. package/dist/types/utils/normalizeRootNode.d.ts.map +1 -0
  26. package/dist/types/utils/valueNodes.d.ts +16 -0
  27. package/dist/types/utils/valueNodes.d.ts.map +1 -0
  28. package/dist/types/visitors/getTypeManifestVisitor.d.ts +1 -1
  29. package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
  30. package/dist/types/visitors/renderVisitor.d.ts.map +1 -1
  31. package/package.json +21 -18
  32. package/readme.md +29 -0
  33. package/src/fragments/accountPage.ts +90 -17
  34. package/src/fragments/instructionPage.ts +103 -77
  35. package/src/fragments/typePage.ts +32 -12
  36. package/src/index.ts +1 -0
  37. package/src/utils/discriminators.ts +121 -0
  38. package/src/utils/exactDecoder.ts +82 -0
  39. package/src/utils/formatCode.ts +7 -11
  40. package/src/utils/index.ts +4 -0
  41. package/src/utils/normalizeRootNode.ts +76 -0
  42. package/src/utils/valueNodes.ts +87 -0
  43. package/src/visitors/getRenderMapVisitor.ts +1 -1
  44. package/src/visitors/getTypeManifestVisitor.ts +14 -8
  45. package/src/visitors/renderVisitor.ts +4 -1
@@ -12,13 +12,71 @@ var errors = require('@codama/errors');
12
12
 
13
13
  // src/visitors/renderVisitor.ts
14
14
  function formatDartDirectory(dir) {
15
- try {
16
- child_process.execSync(`dart format --fix "${dir}"`, {
17
- encoding: "utf-8",
18
- timeout: 6e4,
19
- stdio: ["pipe", "pipe", "pipe"]
20
- });
21
- } catch {
15
+ child_process.execSync(`dart format "${dir}"`, {
16
+ encoding: "utf-8",
17
+ timeout: 6e4,
18
+ stdio: ["pipe", "pipe", "pipe"]
19
+ });
20
+ }
21
+
22
+ // src/utils/normalizeRootNode.ts
23
+ function normalizeRootNode(root) {
24
+ return normalizeNode(root);
25
+ }
26
+ function normalizeNode(value) {
27
+ if (Array.isArray(value)) {
28
+ return value.map(normalizeNode);
29
+ }
30
+ if (!isRecord(value)) {
31
+ return value;
32
+ }
33
+ const normalized = Object.fromEntries(
34
+ Object.entries(value).map(([key, child]) => [key, normalizeNode(child)])
35
+ );
36
+ switch (normalized.kind) {
37
+ case "rootNode":
38
+ setEmptyCollection(normalized, "additionalPrograms");
39
+ break;
40
+ case "programNode":
41
+ setEmptyCollection(normalized, "accounts");
42
+ setEmptyCollection(normalized, "constants");
43
+ setEmptyCollection(normalized, "definedTypes");
44
+ setEmptyCollection(normalized, "errors");
45
+ setEmptyCollection(normalized, "events");
46
+ setEmptyCollection(normalized, "instructions");
47
+ setEmptyCollection(normalized, "pdas");
48
+ break;
49
+ case "instructionNode":
50
+ setEmptyCollection(normalized, "accounts");
51
+ setEmptyCollection(normalized, "arguments");
52
+ break;
53
+ case "pdaNode":
54
+ setEmptyCollection(normalized, "seeds");
55
+ break;
56
+ case "structTypeNode":
57
+ setEmptyCollection(normalized, "fields");
58
+ break;
59
+ case "enumTypeNode":
60
+ setEmptyCollection(normalized, "variants");
61
+ break;
62
+ case "tupleTypeNode":
63
+ setEmptyCollection(normalized, "items");
64
+ break;
65
+ case "hiddenPrefixTypeNode":
66
+ setEmptyCollection(normalized, "prefix");
67
+ break;
68
+ case "hiddenSuffixTypeNode":
69
+ setEmptyCollection(normalized, "suffix");
70
+ break;
71
+ }
72
+ return normalized;
73
+ }
74
+ function isRecord(value) {
75
+ return typeof value === "object" && value !== null;
76
+ }
77
+ function setEmptyCollection(record, key) {
78
+ if (record[key] === void 0) {
79
+ record[key] = [];
22
80
  }
23
81
  }
24
82
 
@@ -208,17 +266,6 @@ function use(typeName, module) {
208
266
  return { content: typeName, imports };
209
267
  }
210
268
 
211
- // src/utils/typeManifest.ts
212
- function emptyTypeManifest() {
213
- return {
214
- type: emptyFragment(),
215
- encoder: emptyFragment(),
216
- decoder: emptyFragment(),
217
- value: emptyFragment(),
218
- isEnum: false
219
- };
220
- }
221
-
222
269
  // src/utils/wellKnownAddresses.ts
223
270
  var WELL_KNOWN_ADDRESSES = /* @__PURE__ */ new Map([
224
271
  // Agave / Solana native programs
@@ -273,6 +320,207 @@ var WELL_KNOWN_ADDRESSES = /* @__PURE__ */ new Map([
273
320
  ["Es9vMFrzaCERm6YKXx6bqKr1Rmd9b5v3J8rM5e4rfF4", "usdtAddress"]
274
321
  ]);
275
322
 
323
+ // src/utils/valueNodes.ts
324
+ function getDartValueFragment(value, dartType) {
325
+ switch (value.kind) {
326
+ case "numberValueNode":
327
+ return fragmentFromString(
328
+ dartType === "BigInt" ? `BigInt.from(${value.number})` : String(value.number)
329
+ );
330
+ case "booleanValueNode":
331
+ return fragmentFromString(String(value.boolean));
332
+ case "stringValueNode":
333
+ return fragmentFromString(toDartStringLiteral(value.string));
334
+ case "noneValueNode":
335
+ return fragmentFromString("null");
336
+ case "bytesValueNode": {
337
+ if (value.encoding !== "base16") {
338
+ throw new Error(
339
+ `Unsupported deterministic bytes encoding: ${value.encoding}`
340
+ );
341
+ }
342
+ const clean = value.data.replace(/^0x/, "");
343
+ if (clean.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(clean)) {
344
+ throw new Error(`Invalid hexadecimal bytes value: ${value.data}`);
345
+ }
346
+ const bytes = clean.match(/.{2}/g)?.map((byte) => `0x${byte}`) ?? [];
347
+ return fragment`${use("Uint8List", "dartTypedData")}.fromList([${fragmentFromString(bytes.join(", "))}])`;
348
+ }
349
+ case "publicKeyValueNode": {
350
+ const wellKnownName = WELL_KNOWN_ADDRESSES.get(value.publicKey);
351
+ return wellKnownName ? fragment`${use(wellKnownName, "solanaAddresses")}` : fragment`${use("Address", "solanaAddresses")}(${fragmentFromString(toDartStringLiteral(value.publicKey))})`;
352
+ }
353
+ default:
354
+ throw new Error(
355
+ `Unsupported deterministic Dart value node: ${value.kind}`
356
+ );
357
+ }
358
+ }
359
+ function isConstDartValueNode(value) {
360
+ switch (value.kind) {
361
+ case "numberValueNode":
362
+ case "booleanValueNode":
363
+ case "stringValueNode":
364
+ case "noneValueNode":
365
+ case "publicKeyValueNode":
366
+ return true;
367
+ default:
368
+ return false;
369
+ }
370
+ }
371
+ function toDartStringLiteral(value) {
372
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t").replaceAll("\b", "\\b").replaceAll("\f", "\\f").replaceAll("$", "\\$").replaceAll("'", "\\'")}'`;
373
+ }
374
+
375
+ // src/utils/discriminators.ts
376
+ function getDiscriminatorValidationFragment(node, scope) {
377
+ const discriminators = node.discriminators ?? [];
378
+ if (discriminators.length === 0) return emptyFragment();
379
+ const fields = getDiscriminatorFields(node);
380
+ const fragments = discriminators.map((discriminator) => {
381
+ switch (discriminator.kind) {
382
+ case "constantDiscriminatorNode":
383
+ return getConstantValidationFragment(
384
+ discriminator.constant,
385
+ discriminator.offset,
386
+ scope
387
+ );
388
+ case "fieldDiscriminatorNode": {
389
+ const field = fields.find(
390
+ (candidate) => candidate.name === discriminator.name
391
+ );
392
+ if (field?.defaultValue == null) {
393
+ throw new Error(
394
+ `Field discriminator "${discriminator.name}" on ${node.kind} "${node.name}" must reference a field with a default value.`
395
+ );
396
+ }
397
+ return getConstantValidationFragment(
398
+ {
399
+ type: field.type,
400
+ value: field.defaultValue
401
+ },
402
+ discriminator.offset,
403
+ scope
404
+ );
405
+ }
406
+ case "sizeDiscriminatorNode": {
407
+ if (!Number.isSafeInteger(discriminator.size) || discriminator.size < 0) {
408
+ throw new Error(
409
+ `Discriminator size must be a non-negative safe integer, got ${discriminator.size}.`
410
+ );
411
+ }
412
+ return fragment`if (bytes.length - offset != ${fragmentFromString(String(discriminator.size))}) {
413
+ throw ${use("SolanaError", "solanaErrors")}(
414
+ ${use("SolanaErrorCode", "solanaErrors")}.codecsInvalidByteLength,
415
+ {
416
+ 'codecDescription': '${fragmentFromString(node.name)} discriminator',
417
+ 'expected': ${fragmentFromString(String(discriminator.size))},
418
+ 'bytesLength': bytes.length - offset,
419
+ },
420
+ );
421
+ }`;
422
+ }
423
+ }
424
+ });
425
+ return mergeFragments(fragments, (contents) => contents.join("\n"));
426
+ }
427
+ function getConstantValidationFragment(constant, discriminatorOffset, scope) {
428
+ if (!Number.isSafeInteger(discriminatorOffset) || discriminatorOffset < 0) {
429
+ throw new Error(
430
+ `Discriminator offset must be a non-negative safe integer, got ${discriminatorOffset}.`
431
+ );
432
+ }
433
+ const manifest = visitorsCore.visit(constant.type, scope.typeManifestVisitor);
434
+ const value = getDartValueFragment(
435
+ constant.value,
436
+ manifest.type.content
437
+ );
438
+ return fragment`${use("getConstantDecoder", "solanaCodecsDataStructures")}(
439
+ ${manifest.encoder}.encode(${value}),
440
+ ).read(bytes, offset + ${fragmentFromString(String(discriminatorOffset))});`;
441
+ }
442
+ function getDiscriminatorFields(node) {
443
+ if (node.kind === "accountNode") {
444
+ return nodes.resolveNestedTypeNode(node.data).fields ?? [];
445
+ }
446
+ return (node.arguments ?? []).map(
447
+ (argument) => ({
448
+ ...argument,
449
+ kind: "structFieldTypeNode"
450
+ })
451
+ );
452
+ }
453
+
454
+ // src/utils/exactDecoder.ts
455
+ function getTopLevelDecoderFragment({
456
+ typeName,
457
+ description,
458
+ discriminatorValidation,
459
+ fromMapFields,
460
+ requireExactConsumption
461
+ }) {
462
+ const validation = discriminatorValidation.content ? ` ${discriminatorValidation.content.replaceAll("\n", "\n ")}
463
+ ` : "";
464
+ const consumptionCheck = requireExactConsumption ? ` if (newOffset != bytes.length) {
465
+ throwInvalidByteLength(newOffset - offset, bytes.length - offset);
466
+ }
467
+ ` : "";
468
+ const fixedSizeCheck = requireExactConsumption ? "bytesLength != structDecoder.fixedSize" : "bytesLength < structDecoder.fixedSize";
469
+ const result = fragment` Never throwInvalidByteLength(int expected, int bytesLength) {
470
+ throw ${use("SolanaError", "solanaErrors")}(
471
+ ${use("SolanaErrorCode", "solanaErrors")}.codecsInvalidByteLength,
472
+ {
473
+ 'codecDescription': '${fragmentFromString(description)}',
474
+ 'expected': expected,
475
+ 'bytesLength': bytesLength,
476
+ },
477
+ );
478
+ }
479
+
480
+ (${fragmentFromString(typeName)}, int) readTopLevel(Uint8List bytes, int offset) {
481
+ ${fragmentFromString(validation)} final (map, newOffset) = structDecoder.read(bytes, offset);
482
+ ${fragmentFromString(consumptionCheck)}
483
+ return (
484
+ ${fragmentFromString(typeName)}(
485
+ ${fragmentFromString(fromMapFields)}
486
+ ),
487
+ newOffset,
488
+ );
489
+ }
490
+
491
+ return switch (structDecoder) {
492
+ ${use("FixedSizeDecoder", "solanaCodecsCore")}<Map<String, Object?>>() =>
493
+ ${use("FixedSizeDecoder", "solanaCodecsCore")}<${fragmentFromString(typeName)}>(
494
+ fixedSize: structDecoder.fixedSize,
495
+ read: (bytes, offset) {
496
+ final bytesLength = bytes.length - offset;
497
+ if (${fragmentFromString(fixedSizeCheck)}) {
498
+ throwInvalidByteLength(structDecoder.fixedSize, bytesLength);
499
+ }
500
+ return readTopLevel(bytes, offset);
501
+ },
502
+ ),
503
+ ${use("VariableSizeDecoder", "solanaCodecsCore")}<Map<String, Object?>>() =>
504
+ ${use("VariableSizeDecoder", "solanaCodecsCore")}<${fragmentFromString(typeName)}>(
505
+ read: readTopLevel,
506
+ maxSize: structDecoder.maxSize,
507
+ ),
508
+ };`;
509
+ result.imports.mergeWith(discriminatorValidation.imports);
510
+ return result;
511
+ }
512
+
513
+ // src/utils/typeManifest.ts
514
+ function emptyTypeManifest() {
515
+ return {
516
+ type: emptyFragment(),
517
+ encoder: emptyFragment(),
518
+ decoder: emptyFragment(),
519
+ value: emptyFragment(),
520
+ isEnum: false
521
+ };
522
+ }
523
+
276
524
  // src/visitors/getTypeManifestVisitor.ts
277
525
  function getTypeManifestVisitor(input) {
278
526
  const { nameApi, linkables, nonScalarEnums = [] } = input;
@@ -416,12 +664,17 @@ function getTypeManifestVisitor(input) {
416
664
  const itemManifest = visitorsCore.visit(node.item, self);
417
665
  const encoderSizeExpr = getArraySizeExpression(node, "Encoder");
418
666
  const decoderSizeExpr = getArraySizeExpression(node, "Decoder");
667
+ const itemType = fragmentFromString(itemManifest.type.content);
668
+ const itemEncoder = fragment`${use(
669
+ "transformEncoder",
670
+ "solanaCodecsCore"
671
+ )}(${itemManifest.encoder}, (${itemType} value) => value)`;
419
672
  return {
420
673
  type: fragment`List<${itemManifest.type}>`,
421
674
  encoder: fragment`${use(
422
675
  "getArrayEncoder",
423
676
  "solanaCodecsDataStructures"
424
- )}(${itemManifest.encoder}${encoderSizeExpr})`,
677
+ )}<${itemType}>(${itemEncoder}${encoderSizeExpr})`,
425
678
  decoder: fragment`${use(
426
679
  "getArrayDecoder",
427
680
  "solanaCodecsDataStructures"
@@ -468,7 +721,7 @@ function getTypeManifestVisitor(input) {
468
721
  };
469
722
  },
470
723
  visitTupleType(node, { self }) {
471
- const itemManifests = node.items.map((item) => visitorsCore.visit(item, self));
724
+ const itemManifests = (node.items ?? []).map((item) => visitorsCore.visit(item, self));
472
725
  const typeList = itemManifests.map((m) => m.type.content).join(", ");
473
726
  const encoderList = itemManifests.map((m) => m.encoder);
474
727
  const decoderList = itemManifests.map((m) => m.decoder);
@@ -501,8 +754,9 @@ function getTypeManifestVisitor(input) {
501
754
  };
502
755
  },
503
756
  visitStructType(node, { self }) {
504
- const fields = node.fields.map((f) => visitorsCore.visit(f, self));
505
- const fieldNames = node.fields.map((f) => f.name);
757
+ const nodeFields = node.fields ?? [];
758
+ const fields = nodeFields.map((f) => visitorsCore.visit(f, self));
759
+ const fieldNames = nodeFields.map((f) => f.name);
506
760
  const typeStr = "Map<String, Object?>";
507
761
  const encoderFields = fields.map((f, i) => `('${fieldNames[i]}', ${f.encoder.content})`).join(", ");
508
762
  const encoderFrag = fragment`${use(
@@ -532,7 +786,7 @@ function getTypeManifestVisitor(input) {
532
786
  return visitorsCore.visit(node.type, self);
533
787
  },
534
788
  visitEnumType(node, { self }) {
535
- const isScalar = node.variants.every(
789
+ const isScalar = (node.variants ?? []).every(
536
790
  (v) => v.kind === "enumEmptyVariantTypeNode"
537
791
  );
538
792
  if (isScalar) {
@@ -624,7 +878,7 @@ function getTypeManifestVisitor(input) {
624
878
  encoder: fragment`${use(
625
879
  "fixEncoderSize",
626
880
  "solanaCodecsCore"
627
- )}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))})`,
881
+ )}(${innerManifest.encoder}, ${fragmentFromString(String(node.size))}, allowTruncation: false)`,
628
882
  decoder: fragment`${use(
629
883
  "fixDecoderSize",
630
884
  "solanaCodecsCore"
@@ -662,7 +916,7 @@ function getTypeManifestVisitor(input) {
662
916
  },
663
917
  visitHiddenPrefixType(node, { self }) {
664
918
  const innerManifest = visitorsCore.visit(node.type, self);
665
- const prefixes = node.prefix.map((p) => getHiddenAffixManifest(p, self));
919
+ const prefixes = (node.prefix ?? []).map((p) => getHiddenAffixManifest(p, self));
666
920
  const prefixEncoders = prefixes.map((p) => p.encoder.content).join(", ");
667
921
  const prefixDecoders = prefixes.map((p) => p.decoder.content).join(", ");
668
922
  const encoderFrag = fragment`${use(
@@ -689,7 +943,7 @@ function getTypeManifestVisitor(input) {
689
943
  },
690
944
  visitHiddenSuffixType(node, { self }) {
691
945
  const innerManifest = visitorsCore.visit(node.type, self);
692
- const suffixes = node.suffix.map((s) => getHiddenAffixManifest(s, self));
946
+ const suffixes = (node.suffix ?? []).map((s) => getHiddenAffixManifest(s, self));
693
947
  const suffixEncoders = suffixes.map((s) => s.encoder.content).join(", ");
694
948
  const suffixDecoders = suffixes.map((s) => s.decoder.content).join(", ");
695
949
  const encoderFrag = fragment`${use(
@@ -976,19 +1230,52 @@ function hexToBytes(hex) {
976
1230
  function getAccountPageFragment(node, scope) {
977
1231
  const name = node.name;
978
1232
  const typeName = scope.nameApi.dataType(name);
979
- const dataNode = nodes.resolveNestedTypeNode(node.data);
980
- const fields = dataNode.fields;
1233
+ const fields = nodes.resolveNestedTypeNode(node.data).fields ?? [];
981
1234
  const fieldManifests = fields.map((f) => ({
982
1235
  field: f,
983
1236
  manifest: visitorsCore.visit(f.type, scope.typeManifestVisitor)
984
1237
  }));
1238
+ const omittedFields = fields.filter(isOmittedDefaultField);
1239
+ const constructorFields = fields.filter(
1240
+ (field) => !isOmittedDefaultField(field)
1241
+ );
1242
+ const forcedFields = /* @__PURE__ */ new Set([
1243
+ ...omittedFields,
1244
+ ...fields.filter((field) => isFieldDiscriminator(field, node))
1245
+ ]);
1246
+ const fieldDefaults = new Map(
1247
+ fieldManifests.flatMap(({ field, manifest }) => {
1248
+ if (!forcedFields.has(field) || field.defaultValue == null) return [];
1249
+ return [
1250
+ [
1251
+ field,
1252
+ getDartValueFragment(field.defaultValue, manifest.type.content)
1253
+ ]
1254
+ ];
1255
+ })
1256
+ );
1257
+ for (const field of forcedFields) {
1258
+ if (!fieldDefaults.has(field)) {
1259
+ throw new Error(
1260
+ `Forced field "${field.name}" on account "${node.name}" must have a deterministic default value.`
1261
+ );
1262
+ }
1263
+ }
985
1264
  const fieldDecls = fieldManifests.map(({ field: f, manifest }) => {
986
1265
  return ` final ${manifest.type.content} ${camelCase(f.name)};`;
987
1266
  }).join("\n");
988
- const ctorParams = fields.map((f) => ` required this.${camelCase(f.name)},`).join("\n");
989
- const ctorSignature = fields.length === 0 ? `const ${typeName}();` : `const ${typeName}({
1267
+ const ctorParams = constructorFields.map((f) => ` required this.${camelCase(f.name)},`).join("\n");
1268
+ const ctorInitializers = omittedFields.map((field) => {
1269
+ const fieldName = camelCase(field.name);
1270
+ return `${fieldName} = ${fieldDefaults.get(field).content}`;
1271
+ }).join(",\n ");
1272
+ const ctorKeyword = omittedFields.every(
1273
+ (field) => isConstDartValueNode(field.defaultValue)
1274
+ ) ? "const " : "";
1275
+ const ctorSignature = constructorFields.length === 0 ? `${ctorKeyword}${typeName}()${ctorInitializers ? ` : ${ctorInitializers}` : ""};` : `${ctorKeyword}${typeName}({
990
1276
  ${ctorParams}
991
- });`;
1277
+ })${ctorInitializers ? ` :
1278
+ ${ctorInitializers}` : ""};`;
992
1279
  const eqChecks = fields.length === 0 ? "true" : fields.map(
993
1280
  (f) => `${camelCase(f.name)} == other.${camelCase(f.name)}`
994
1281
  ).join(" &&\n ");
@@ -1003,10 +1290,11 @@ ${ctorParams}
1003
1290
  const decFields = fieldManifests.map(({ field: f, manifest }) => {
1004
1291
  return ` ('${f.name}', ${manifest.decoder.content}),`;
1005
1292
  }).join("\n");
1006
- const toMapFields = fields.map(
1007
- (f) => ` '${f.name}': value.${camelCase(f.name)},`
1008
- ).join("\n");
1009
- const fromMapFields = fieldManifests.map(({ field: f, manifest }) => {
1293
+ const toMapFields = fields.map((f) => {
1294
+ const value = forcedFields.has(f) ? fieldDefaults.get(f).content : `value.${camelCase(f.name)}`;
1295
+ return ` '${f.name}': ${value},`;
1296
+ }).join("\n");
1297
+ const fromMapFields = fieldManifests.filter(({ field }) => !isOmittedDefaultField(field)).map(({ field: f, manifest }) => {
1010
1298
  const typeStr = manifest.type.content;
1011
1299
  const isNullable = typeStr.endsWith("?");
1012
1300
  const accessor = isNullable ? `map['${f.name}']` : `map['${f.name}']!`;
@@ -1020,6 +1308,19 @@ ${ctorParams}
1020
1308
  /// The size of the [${fragmentFromString(typeName)}] account data in bytes.
1021
1309
  const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(String(node.size))};` : emptyFragment();
1022
1310
  const discFragment = getDiscriminatorConstantsFragment(node, scope);
1311
+ const discriminatorValidation = getDiscriminatorValidationFragment(
1312
+ node,
1313
+ scope
1314
+ );
1315
+ const topLevelDecoder = getTopLevelDecoderFragment({
1316
+ typeName,
1317
+ description: `${name} account decoder`,
1318
+ discriminatorValidation,
1319
+ fromMapFields,
1320
+ requireExactConsumption: (node.discriminators ?? []).some(
1321
+ (discriminator) => discriminator.kind === "sizeDiscriminatorNode"
1322
+ )
1323
+ });
1023
1324
  const parts = [
1024
1325
  fragment`// Auto-generated. Do not edit.
1025
1326
  // ignore_for_file: type=lint
@@ -1079,12 +1380,7 @@ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
1079
1380
  ${fragmentFromString(decFields)}
1080
1381
  ]);
1081
1382
 
1082
- return transformDecoder(
1083
- structDecoder,
1084
- (Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}(
1085
- ${fragmentFromString(fromMapFields)}
1086
- ),
1087
- );
1383
+ ${topLevelDecoder}
1088
1384
  }
1089
1385
 
1090
1386
  Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
@@ -1100,8 +1396,19 @@ Account<${fragmentFromString(typeName)}> ${fragmentFromString(decodeFnName)}(Enc
1100
1396
  result.imports.mergeWith(manifest.decoder.imports);
1101
1397
  result.imports.mergeWith(manifest.type.imports);
1102
1398
  }
1399
+ for (const defaultValue of fieldDefaults.values()) {
1400
+ result.imports.mergeWith(defaultValue.imports);
1401
+ }
1103
1402
  return result;
1104
1403
  }
1404
+ function isOmittedDefaultField(field) {
1405
+ return field.defaultValue != null && field.defaultValueStrategy === "omitted";
1406
+ }
1407
+ function isFieldDiscriminator(field, node) {
1408
+ return (node.discriminators ?? []).some(
1409
+ (discriminator) => discriminator.kind === "fieldDiscriminatorNode" && discriminator.name === field.name
1410
+ );
1411
+ }
1105
1412
 
1106
1413
  // src/fragments/errorPage.ts
1107
1414
  function getErrorPageFragment(programNode, scope) {
@@ -1162,7 +1469,7 @@ function getIndexPageFragment(fileNames, relativePath = "") {
1162
1469
  "// Auto-generated. Do not edit.\n// ignore_for_file: type=lint\n"
1163
1470
  );
1164
1471
  }
1165
- const exports$1 = fileNames.sort().map((f) => {
1472
+ const exports = fileNames.sort().map((f) => {
1166
1473
  const path = relativePath ? `${relativePath}/${f}` : f;
1167
1474
  return `export '${path}';`;
1168
1475
  }).join("\n");
@@ -1170,17 +1477,17 @@ function getIndexPageFragment(fileNames, relativePath = "") {
1170
1477
  `// Auto-generated. Do not edit.
1171
1478
  // ignore_for_file: type=lint
1172
1479
 
1173
- ${exports$1}
1480
+ ${exports}
1174
1481
  `
1175
1482
  );
1176
1483
  }
1177
1484
  function getRootIndexPageFragment(categories) {
1178
- const exports$1 = categories.sort().map((cat) => `export '${cat}/${cat}.dart';`).join("\n");
1485
+ const exports = categories.sort().map((cat) => `export '${cat}/${cat}.dart';`).join("\n");
1179
1486
  return fragmentFromString(
1180
1487
  `// Auto-generated. Do not edit.
1181
1488
  // ignore_for_file: type=lint
1182
1489
 
1183
- ${exports$1}
1490
+ ${exports}
1184
1491
  `
1185
1492
  );
1186
1493
  }
@@ -1190,30 +1497,62 @@ function getInstructionPageFragment(node, scope) {
1190
1497
  const instrFnName = scope.nameApi.instructionFunction(name);
1191
1498
  const parseFnName = scope.nameApi.instructionParseFunction(name);
1192
1499
  const accounts = node.accounts ?? [];
1193
- const args = (node.arguments ?? []).filter(
1194
- (arg) => !isDiscriminatorArg(arg, node)
1195
- );
1196
1500
  const allArgs = node.arguments ?? [];
1501
+ const optionalAccountStrategy = node.optionalAccountStrategy ?? "programId";
1502
+ if (optionalAccountStrategy !== "programId" && optionalAccountStrategy !== "omitted") {
1503
+ throw new Error(
1504
+ `Unsupported optional account strategy on instruction "${node.name}": ${optionalAccountStrategy}`
1505
+ );
1506
+ }
1507
+ const args = allArgs.filter(
1508
+ (arg) => !isDiscriminatorArg(arg, node) && !isOmittedDefaultArgument(arg)
1509
+ );
1197
1510
  const dataClassName = `${typeName}InstructionData`;
1198
- const allArgManifests = allArgs.map((arg) => ({
1511
+ const allArgManifests = (node.arguments ?? []).map((arg) => ({
1199
1512
  arg,
1200
1513
  manifest: visitorsCore.visit(arg.type, scope.typeManifestVisitor)
1201
1514
  }));
1515
+ const argDefaultValues = new Map(
1516
+ allArgManifests.flatMap(({ arg, manifest }) => {
1517
+ if (arg.defaultValue == null) return [];
1518
+ const needsRenderedDefault = isOmittedDefaultArgument(arg) || isDiscriminatorArg(arg, node) || arg.defaultValue.kind !== "accountBumpValueNode";
1519
+ if (!needsRenderedDefault) return [];
1520
+ return [
1521
+ [
1522
+ arg,
1523
+ getDartValueFragment(arg.defaultValue, manifest.type.content)
1524
+ ]
1525
+ ];
1526
+ })
1527
+ );
1528
+ for (const arg of allArgs.filter(
1529
+ (candidate) => isDiscriminatorArg(candidate, node)
1530
+ )) {
1531
+ if (!argDefaultValues.has(arg)) {
1532
+ throw new Error(
1533
+ `Field discriminator "${arg.name}" on instruction "${node.name}" must reference a field with a default value.`
1534
+ );
1535
+ }
1536
+ }
1202
1537
  const dataFieldDecls = allArgManifests.map(({ arg, manifest }) => {
1203
1538
  const fieldName = camelCase(arg.name);
1204
1539
  return ` final ${manifest.type.content} ${fieldName};`;
1205
1540
  }).join("\n");
1206
1541
  const dataCtorParams = allArgManifests.map(({ arg, manifest }) => {
1207
1542
  const fieldName = camelCase(arg.name);
1543
+ if (isOmittedDefaultArgument(arg)) return "";
1208
1544
  if (isDiscriminatorArg(arg, node)) {
1209
- return isConstDefaultValue(arg.defaultValue) ? ` this.${fieldName} = ${getDiscriminatorDefault(arg)},` : ` ${manifest.type.content}? ${fieldName},`;
1545
+ return isConstDefaultValue(arg.defaultValue) ? ` this.${fieldName} = ${argDefaultValues.get(arg).content},` : ` ${manifest.type.content}? ${fieldName},`;
1210
1546
  }
1211
1547
  return ` required this.${fieldName},`;
1212
- }).join("\n");
1548
+ }).filter(Boolean).join("\n");
1213
1549
  const dataCtorInitializers = allArgManifests.map(({ arg }) => {
1214
1550
  const fieldName = camelCase(arg.name);
1551
+ if (isOmittedDefaultArgument(arg)) {
1552
+ return ` ${fieldName} = ${argDefaultValues.get(arg).content}`;
1553
+ }
1215
1554
  if (isDiscriminatorArg(arg, node) && !isConstDefaultValue(arg.defaultValue)) {
1216
- return ` ${fieldName} = ${fieldName} ?? ${getDiscriminatorDefault(arg)}`;
1555
+ return ` ${fieldName} = ${fieldName} ?? ${argDefaultValues.get(arg).content}`;
1217
1556
  }
1218
1557
  return null;
1219
1558
  }).filter((value) => value !== null).join(",\n");
@@ -1223,16 +1562,26 @@ function getInstructionPageFragment(node, scope) {
1223
1562
  const decFields = allArgManifests.map(({ arg, manifest }) => {
1224
1563
  return ` ('${arg.name}', ${manifest.decoder.content}),`;
1225
1564
  }).join("\n");
1226
- const toMapFields = allArgs.map(
1227
- (arg) => ` '${arg.name}': value.${camelCase(arg.name)},`
1228
- ).join("\n");
1229
- const fromMapFields = allArgManifests.map(({ arg, manifest }) => {
1565
+ const toMapFields = allArgs.map((arg) => {
1566
+ const value = isOmittedDefaultArgument(arg) || isDiscriminatorArg(arg, node) ? argDefaultValues.get(arg).content : `value.${camelCase(arg.name)}`;
1567
+ return ` '${arg.name}': ${value},`;
1568
+ }).join("\n");
1569
+ const fromMapFields = allArgManifests.filter(({ arg }) => !isOmittedDefaultArgument(arg)).map(({ arg, manifest }) => {
1230
1570
  const typeStr = manifest.type.content;
1231
1571
  const isNullable = typeStr.endsWith("?");
1232
1572
  const accessor = isNullable ? `map['${arg.name}']` : `map['${arg.name}']!`;
1233
1573
  return ` ${camelCase(arg.name)}: ${accessor} as ${typeStr},`;
1234
1574
  }).join("\n");
1235
- const dataCtorKeyword = dataCtorInitializers ? "" : "const ";
1575
+ const dataCtorKeyword = allArgs.every(
1576
+ (arg) => !isOmittedDefaultArgument(arg) || isConstDartValueNode(arg.defaultValue)
1577
+ ) && allArgs.every(
1578
+ (arg) => !isDiscriminatorArg(arg, node) || isConstDefaultValue(arg.defaultValue)
1579
+ ) ? "const " : "";
1580
+ const dataCtorSignature = dataCtorParams ? `${dataCtorKeyword}${dataClassName}({
1581
+ ${dataCtorParams}
1582
+ })${dataCtorInitializers ? ` :
1583
+ ${dataCtorInitializers}` : ""};` : `${dataCtorKeyword}${dataClassName}()${dataCtorInitializers ? ` :
1584
+ ${dataCtorInitializers}` : ""};`;
1236
1585
  const dataEncoderName = `get${typeName}InstructionDataEncoder`;
1237
1586
  const dataDecoderName = `get${typeName}InstructionDataDecoder`;
1238
1587
  const dataCodecName = `get${typeName}InstructionDataCodec`;
@@ -1262,20 +1611,33 @@ function getInstructionPageFragment(node, scope) {
1262
1611
  const role = getAccountRole(acc);
1263
1612
  const isOptional = acc.isOptional ?? false;
1264
1613
  if (isOptional) {
1265
- return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}),`;
1614
+ if (optionalAccountStrategy === "omitted") {
1615
+ return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}),`;
1616
+ }
1617
+ return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}) else AccountMeta(address: ${instrProgramParam}, role: AccountRole.readonly),`;
1266
1618
  }
1267
1619
  return ` AccountMeta(address: ${fieldName}, role: ${role}),`;
1268
1620
  }).join("\n");
1269
1621
  const dataConstruction = allArgs.map((arg) => {
1270
1622
  const fieldName = camelCase(arg.name);
1271
- if (isDiscriminatorArg(arg, node)) {
1623
+ if (isOmittedDefaultArgument(arg) || isDiscriminatorArg(arg, node)) {
1272
1624
  return "";
1273
1625
  }
1274
- const typeStr2 = argManifestMap.get(arg)?.type.content;
1275
1626
  const skipDefault = arg.defaultValue?.kind === "accountBumpValueNode";
1276
- return ` ${fieldName}: ${fieldName}${arg.defaultValue != null && !skipDefault ? ` ?? ${getDefaultValue(arg, typeStr2)}` : ""},`;
1627
+ return ` ${fieldName}: ${fieldName}${arg.defaultValue != null && !skipDefault ? ` ?? ${argDefaultValues.get(arg).content}` : ""},`;
1277
1628
  }).filter(Boolean).join("\n");
1278
1629
  const discFragment = getDiscriminatorConstantsFragment(node, scope);
1630
+ const discriminatorValidation = getDiscriminatorValidationFragment(
1631
+ node,
1632
+ scope
1633
+ );
1634
+ const topLevelDecoder = getTopLevelDecoderFragment({
1635
+ typeName: dataClassName,
1636
+ description: `${name} instruction decoder`,
1637
+ discriminatorValidation,
1638
+ fromMapFields,
1639
+ requireExactConsumption: true
1640
+ });
1279
1641
  const parts = [
1280
1642
  fragment`// Auto-generated. Do not edit.
1281
1643
  // ignore_for_file: type=lint
@@ -1299,10 +1661,7 @@ ${use("AccountRole", "solanaInstructions")}`
1299
1661
  parts.push(fragment`
1300
1662
  @immutable
1301
1663
  class ${fragmentFromString(dataClassName)} {
1302
- ${fragmentFromString(dataCtorKeyword)}${fragmentFromString(dataClassName)}({
1303
- ${fragmentFromString(dataCtorParams)}
1304
- })${fragmentFromString(dataCtorInitializers ? ` :
1305
- ${dataCtorInitializers}` : "")};
1664
+ ${fragmentFromString(dataCtorSignature)}
1306
1665
 
1307
1666
  ${fragmentFromString(dataFieldDecls)}
1308
1667
  }`);
@@ -1325,12 +1684,7 @@ Decoder<${fragmentFromString(dataClassName)}> ${fragmentFromString(dataDecoderNa
1325
1684
  ${fragmentFromString(decFields)}
1326
1685
  ]);
1327
1686
 
1328
- return transformDecoder(
1329
- structDecoder,
1330
- (Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(dataClassName)}(
1331
- ${fragmentFromString(fromMapFields)}
1332
- ),
1333
- );
1687
+ ${topLevelDecoder}
1334
1688
  }
1335
1689
 
1336
1690
  Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}> ${fragmentFromString(dataCodecName)}() {
@@ -1366,6 +1720,9 @@ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruct
1366
1720
  result.imports.mergeWith(manifest.decoder.imports);
1367
1721
  result.imports.mergeWith(manifest.type.imports);
1368
1722
  }
1723
+ for (const defaultValue of argDefaultValues.values()) {
1724
+ result.imports.mergeWith(defaultValue.imports);
1725
+ }
1369
1726
  return result;
1370
1727
  }
1371
1728
  function getAccountRole(acc) {
@@ -1382,19 +1739,6 @@ function isDiscriminatorArg(arg, node) {
1382
1739
  (d) => d.kind === "fieldDiscriminatorNode" && d.name === arg.name
1383
1740
  );
1384
1741
  }
1385
- function getDiscriminatorDefault(arg, _node) {
1386
- if (arg.defaultValue) {
1387
- if (arg.defaultValue.kind === "numberValueNode") {
1388
- return String(arg.defaultValue.number);
1389
- }
1390
- if (arg.defaultValue.kind === "bytesValueNode") {
1391
- const data = arg.defaultValue.data;
1392
- const bytes = data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
1393
- return `Uint8List.fromList([${bytes.join(", ")}])`;
1394
- }
1395
- }
1396
- return "0";
1397
- }
1398
1742
  function isConstDefaultValue(defaultValue) {
1399
1743
  if (!defaultValue) return false;
1400
1744
  switch (defaultValue.kind) {
@@ -1407,34 +1751,8 @@ function isConstDefaultValue(defaultValue) {
1407
1751
  return false;
1408
1752
  }
1409
1753
  }
1410
- function getDefaultValue(arg, typeStr) {
1411
- if (!arg.defaultValue) return "null";
1412
- const isBigInt = typeStr === "BigInt";
1413
- const dv = arg.defaultValue;
1414
- switch (dv.kind) {
1415
- case "numberValueNode":
1416
- return isBigInt ? `BigInt.from(${dv.number})` : String(dv.number);
1417
- case "booleanValueNode":
1418
- return String(dv.boolean);
1419
- case "stringValueNode":
1420
- return `'${dv.string}'`;
1421
- case "publicKeyValueNode": {
1422
- const wellKnownName = WELL_KNOWN_ADDRESSES.get(dv.publicKey);
1423
- if (wellKnownName) {
1424
- use(wellKnownName, "solanaAddresses");
1425
- return wellKnownName;
1426
- }
1427
- return `Address('${dv.publicKey}')`;
1428
- }
1429
- case "noneValueNode":
1430
- return "null";
1431
- case "bytesValueNode": {
1432
- const bytes = dv.data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
1433
- return `Uint8List.fromList([${bytes.join(", ")}])`;
1434
- }
1435
- default:
1436
- return "null";
1437
- }
1754
+ function isOmittedDefaultArgument(arg) {
1755
+ return arg.defaultValue != null && arg.defaultValueStrategy === "omitted";
1438
1756
  }
1439
1757
  function getPdaPageFragment(node, scope) {
1440
1758
  const name = node.name;
@@ -1729,25 +2047,26 @@ function getTypePageFragment(node, scope) {
1729
2047
  scope.nameApi.dataType(name);
1730
2048
  const typeNode = node.type;
1731
2049
  if (typeNode.kind === "enumTypeNode") {
1732
- const isScalar = typeNode.variants.every(
2050
+ const variants = typeNode.variants ?? [];
2051
+ const isScalar = variants.every(
1733
2052
  (v) => v.kind === "enumEmptyVariantTypeNode"
1734
2053
  );
1735
2054
  if (isScalar) {
1736
- return getScalarEnumPageFragment(node, scope);
2055
+ return getScalarEnumPageFragment(node, variants, scope);
1737
2056
  }
1738
- return getDataEnumPageFragment(node, scope);
2057
+ return getDataEnumPageFragment(node, variants, scope);
1739
2058
  }
1740
2059
  if (typeNode.kind === "structTypeNode") {
1741
2060
  return getStructPageFragment(node, scope);
1742
2061
  }
1743
2062
  return getTypeAliasPageFragment(node, scope);
1744
2063
  }
1745
- function getScalarEnumPageFragment(node, scope) {
2064
+ function getScalarEnumPageFragment(node, enumVariants, scope) {
1746
2065
  const name = node.name;
1747
2066
  const typeName = scope.nameApi.dataType(name);
1748
2067
  const enumNode = node.type;
1749
2068
  if (enumNode.kind !== "enumTypeNode") return emptyFragment();
1750
- const variants = enumNode.variants.map((v) => {
2069
+ const variants = enumVariants.map((v) => {
1751
2070
  const variantName = scope.nameApi.enumVariant(v.name);
1752
2071
  return variantName;
1753
2072
  });
@@ -1758,6 +2077,14 @@ function getScalarEnumPageFragment(node, scope) {
1758
2077
  const resolvedSize = nodes.resolveNestedTypeNode(enumNode.size);
1759
2078
  const sizeType = resolvedSize.format ?? "u8";
1760
2079
  const sizeCodecName = getNumberCodecName2(sizeType);
2080
+ const isU64 = sizeType === "u64";
2081
+ const encodedVariantIndex = isU64 ? "BigInt.from(value.index)" : "value.index";
2082
+ const decodedVariant = isU64 ? `(BigInt value, Uint8List bytes, int offset) {
2083
+ if (value.isNegative || value >= BigInt.from(${typeName}.values.length)) {
2084
+ throw RangeError('Invalid ${typeName} discriminator: ' + value.toString());
2085
+ }
2086
+ return ${typeName}.values[value.toInt()];
2087
+ }` : `(int value, Uint8List bytes, int offset) => ${typeName}.values[value]`;
1761
2088
  return fragment`// Auto-generated. Do not edit.
1762
2089
  // ignore_for_file: type=lint
1763
2090
 
@@ -1778,14 +2105,14 @@ ${fragmentFromString(variantLines)}
1778
2105
  Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
1779
2106
  return transformEncoder(
1780
2107
  get${fragmentFromString(sizeCodecName)}Encoder(),
1781
- (${fragmentFromString(typeName)} value) => value.index,
2108
+ (${fragmentFromString(typeName)} value) => ${fragmentFromString(encodedVariantIndex)},
1782
2109
  );
1783
2110
  }
1784
2111
 
1785
2112
  Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
1786
2113
  return transformDecoder(
1787
2114
  get${fragmentFromString(sizeCodecName)}Decoder(),
1788
- (int value, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}.values[value],
2115
+ ${fragmentFromString(decodedVariant)},
1789
2116
  );
1790
2117
  }
1791
2118
 
@@ -1793,7 +2120,7 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
1793
2120
  return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
1794
2121
  }`;
1795
2122
  }
1796
- function getDataEnumPageFragment(node, scope) {
2123
+ function getDataEnumPageFragment(node, variants, scope) {
1797
2124
  const name = node.name;
1798
2125
  const typeName = scope.nameApi.dataType(name);
1799
2126
  const enumNode = node.type;
@@ -1804,8 +2131,8 @@ function getDataEnumPageFragment(node, scope) {
1804
2131
  const encodeCases = [];
1805
2132
  const decodeCases = [];
1806
2133
  const allVariantManifests = [];
1807
- for (let i = 0; i < enumNode.variants.length; i++) {
1808
- const variant = enumNode.variants[i];
2134
+ for (let i = 0; i < variants.length; i++) {
2135
+ const variant = variants[i];
1809
2136
  const variantName = pascalCase(variant.name);
1810
2137
  const variantClassName = scope.nameApi.sealedClassVariant(
1811
2138
  name,
@@ -1835,7 +2162,7 @@ function getDataEnumPageFragment(node, scope) {
1835
2162
  decodeCases.push(`case ${i}: return const ${variantClassName}();`);
1836
2163
  } else if (variant.kind === "enumStructVariantTypeNode") {
1837
2164
  const resolvedStruct = nodes.resolveNestedTypeNode(variant.struct);
1838
- const fields = resolvedStruct.fields;
2165
+ const fields = resolvedStruct.fields ?? [];
1839
2166
  const fieldManifests = fields.map((f) => ({
1840
2167
  field: f,
1841
2168
  manifest: visitorsCore.visit(f.type, scope.typeManifestVisitor)
@@ -1901,7 +2228,7 @@ ${fieldDecls}
1901
2228
  decodeCases.push(`case ${i}: return ${variantClassName}(${fromMapFields.replace(/\n/g, " ").replace(/,$/, "")});`);
1902
2229
  } else if (variant.kind === "enumTupleVariantTypeNode") {
1903
2230
  const resolvedTuple = nodes.resolveNestedTypeNode(variant.tuple);
1904
- const items = resolvedTuple.items;
2231
+ const items = resolvedTuple.items ?? [];
1905
2232
  if (items.length === 1) {
1906
2233
  const manifest = visitorsCore.visit(items[0], scope.typeManifestVisitor);
1907
2234
  allVariantManifests.push(manifest);
@@ -2001,7 +2328,7 @@ function getStructPageFragment(node, scope) {
2001
2328
  const typeName = scope.nameApi.dataType(name);
2002
2329
  const structNode = node.type;
2003
2330
  if (structNode.kind !== "structTypeNode") return emptyFragment();
2004
- const fields = structNode.fields;
2331
+ const fields = structNode.fields ?? [];
2005
2332
  const fieldManifests = fields.map((f) => ({
2006
2333
  field: f,
2007
2334
  manifest: visitorsCore.visit(f.type, scope.typeManifestVisitor)
@@ -2208,7 +2535,7 @@ function getRenderMapVisitor(options = {}) {
2208
2535
  visitRoot(node, { self }) {
2209
2536
  const programMaps = [
2210
2537
  visitorsCore.visit(node.program, self),
2211
- ...node.additionalPrograms.map((p) => visitorsCore.visit(p, self))
2538
+ ...(node.additionalPrograms ?? []).map((p) => visitorsCore.visit(p, self))
2212
2539
  ];
2213
2540
  const mergedProgramMap = renderersCore.mergeRenderMaps(programMaps);
2214
2541
  const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
@@ -2354,11 +2681,12 @@ function renderVisitor(outputDir, options = {}) {
2354
2681
  dependencyMap
2355
2682
  } = options;
2356
2683
  return visitorsCore.rootNodeVisitor((root) => {
2684
+ const normalizedRoot = normalizeRootNode(root);
2357
2685
  if (deleteFolderBeforeRendering && fs.existsSync(outputDir)) {
2358
2686
  renderersCore.deleteDirectory(outputDir);
2359
2687
  }
2360
2688
  const renderMap = visitorsCore.visit(
2361
- root,
2689
+ normalizedRoot,
2362
2690
  getRenderMapVisitor({ nameApi, dependencyMap })
2363
2691
  );
2364
2692
  const typePathMap = {};
@@ -2446,6 +2774,7 @@ exports.createDartNameApi = createDartNameApi;
2446
2774
  exports.default = src_default;
2447
2775
  exports.getRenderMapVisitor = getRenderMapVisitor;
2448
2776
  exports.getTypeManifestVisitor = getTypeManifestVisitor;
2777
+ exports.normalizeRootNode = normalizeRootNode;
2449
2778
  exports.renderVisitor = renderVisitor;
2450
2779
  //# sourceMappingURL=index.browser.cjs.map
2451
2780
  //# sourceMappingURL=index.browser.cjs.map