@ic-reactor/candid 3.0.14-beta.0 → 3.0.14-beta.2

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 (33) hide show
  1. package/dist/metadata-display-reactor.d.ts +66 -11
  2. package/dist/metadata-display-reactor.d.ts.map +1 -1
  3. package/dist/metadata-display-reactor.js +48 -12
  4. package/dist/metadata-display-reactor.js.map +1 -1
  5. package/dist/visitor/arguments/helpers.d.ts +22 -7
  6. package/dist/visitor/arguments/helpers.d.ts.map +1 -1
  7. package/dist/visitor/arguments/helpers.js +39 -5
  8. package/dist/visitor/arguments/helpers.js.map +1 -1
  9. package/dist/visitor/arguments/index.d.ts.map +1 -1
  10. package/dist/visitor/arguments/index.js +26 -34
  11. package/dist/visitor/arguments/index.js.map +1 -1
  12. package/dist/visitor/arguments/types.d.ts +120 -132
  13. package/dist/visitor/arguments/types.d.ts.map +1 -1
  14. package/dist/visitor/arguments/types.js +25 -1
  15. package/dist/visitor/arguments/types.js.map +1 -1
  16. package/dist/visitor/constants.d.ts.map +1 -1
  17. package/dist/visitor/constants.js +19 -17
  18. package/dist/visitor/constants.js.map +1 -1
  19. package/dist/visitor/returns/index.d.ts.map +1 -1
  20. package/dist/visitor/returns/index.js +24 -13
  21. package/dist/visitor/returns/index.js.map +1 -1
  22. package/dist/visitor/returns/types.d.ts +48 -10
  23. package/dist/visitor/returns/types.d.ts.map +1 -1
  24. package/package.json +2 -2
  25. package/src/metadata-display-reactor.ts +86 -14
  26. package/src/visitor/arguments/helpers.ts +50 -8
  27. package/src/visitor/arguments/index.test.ts +105 -61
  28. package/src/visitor/arguments/index.ts +38 -36
  29. package/src/visitor/arguments/types.ts +211 -220
  30. package/src/visitor/constants.ts +24 -15
  31. package/src/visitor/returns/index.test.ts +30 -30
  32. package/src/visitor/returns/index.ts +52 -21
  33. package/src/visitor/returns/types.ts +60 -17
@@ -1,5 +1,5 @@
1
1
  import {
2
- ArgumentFieldType,
2
+ VisitorDataType,
3
3
  CompoundField,
4
4
  FieldNode,
5
5
  FieldByType,
@@ -9,12 +9,16 @@ import {
9
9
  VariantField,
10
10
  } from "./types"
11
11
 
12
+ // ════════════════════════════════════════════════════════════════════════════
13
+ // Type Guards
14
+ // ════════════════════════════════════════════════════════════════════════════
15
+
12
16
  /**
13
17
  * Type guard for checking specific field types.
14
18
  *
15
19
  * @example
16
20
  * ```tsx
17
- * function FieldInput({ field }: { field: Field }) {
21
+ * function FieldInput({ field }: { field: FieldNode }) {
18
22
  * if (isFieldType(field, 'record')) {
19
23
  * // field is now typed as RecordField
20
24
  * return <RecordInput field={field} />
@@ -27,14 +31,17 @@ import {
27
31
  * }
28
32
  * ```
29
33
  */
30
- export function isFieldType<T extends ArgumentFieldType>(
34
+ export function isFieldType<T extends VisitorDataType>(
31
35
  field: FieldNode,
32
36
  type: T
33
37
  ): field is FieldByType<T> {
34
38
  return field.type === type
35
39
  }
36
40
 
37
- /** Check if a field is a compound type (contains other fields) */
41
+ /**
42
+ * Check if a field is a compound type (contains other fields).
43
+ * Compound types: record, variant, tuple, optional, vector, recursive
44
+ */
38
45
  export function isCompoundField(field: FieldNode): field is CompoundField {
39
46
  return [
40
47
  "record",
@@ -46,16 +53,38 @@ export function isCompoundField(field: FieldNode): field is CompoundField {
46
53
  ].includes(field.type)
47
54
  }
48
55
 
49
- /** Check if a field is a primitive type */
56
+ /**
57
+ * Check if a field is a primitive type.
58
+ * Primitive types: principal, number, text, boolean, null
59
+ */
50
60
  export function isPrimitiveField(field: FieldNode): field is PrimitiveField {
51
61
  return ["principal", "number", "text", "boolean", "null"].includes(field.type)
52
62
  }
53
63
 
54
- /** Check if a field has children (for iteration) */
64
+ /**
65
+ * Check if a field has child fields array (for iteration).
66
+ * Applies to: record (fields), tuple (fields)
67
+ */
55
68
  export function hasChildFields(
56
69
  field: FieldNode
57
- ): field is RecordField | VariantField | TupleField {
58
- return "fields" in field && Array.isArray((field as RecordField).fields)
70
+ ): field is RecordField | TupleField {
71
+ return (
72
+ (field.type === "record" || field.type === "tuple") &&
73
+ "fields" in field &&
74
+ Array.isArray((field as RecordField).fields)
75
+ )
76
+ }
77
+
78
+ /**
79
+ * Check if a field has variant options (for iteration).
80
+ * Applies to: variant (options)
81
+ */
82
+ export function hasOptions(field: FieldNode): field is VariantField {
83
+ return (
84
+ field.type === "variant" &&
85
+ "options" in field &&
86
+ Array.isArray((field as VariantField).options)
87
+ )
59
88
  }
60
89
 
61
90
  // ════════════════════════════════════════════════════════════════════════════
@@ -72,6 +101,7 @@ export function hasChildFields(
72
101
  * formatLabel("_0_") // "Item 0"
73
102
  * formatLabel("created_at") // "Created At"
74
103
  * formatLabel("userAddress") // "User Address"
104
+ * formatLabel("__ret0") // "Result 0"
75
105
  * ```
76
106
  */
77
107
  export function formatLabel(label: string): string {
@@ -81,12 +111,24 @@ export function formatLabel(label: string): string {
81
111
  return `Arg ${num}`
82
112
  }
83
113
 
114
+ // Handle return labels: __ret0 -> Result 0
115
+ if (label.startsWith("__ret")) {
116
+ const num = label.slice(5)
117
+ return `Result ${num}`
118
+ }
119
+
84
120
  // Handle tuple index labels: _0_ -> Item 0
85
121
  if (/^_\d+_$/.test(label)) {
86
122
  const num = label.slice(1, -1)
87
123
  return `Item ${num}`
88
124
  }
89
125
 
126
+ // Handle simple index labels: _0 -> Item 0
127
+ if (/^_\d+$/.test(label)) {
128
+ const num = label.slice(1)
129
+ return `Item ${num}`
130
+ }
131
+
90
132
  // Handle item labels for vectors: label_item -> Item
91
133
  if (label.endsWith("_item")) {
92
134
  return "Item"
@@ -148,8 +148,8 @@ describe("ArgumentFieldVisitor", () => {
148
148
  expect(field.type).toBe("record")
149
149
  expect(field.label).toBe("person")
150
150
  expect(field.fields).toHaveLength(2)
151
- expect(field.fieldMap.has("name")).toBe(true)
152
- expect(field.fieldMap.has("age")).toBe(true)
151
+ expect(field.fields.some((f) => f.label === "name")).toBe(true)
152
+ expect(field.fields.some((f) => f.label === "age")).toBe(true)
153
153
 
154
154
  const nameField = field.fields.find((f) => f.label === "name")
155
155
  if (!nameField || nameField.type !== "text") {
@@ -289,14 +289,14 @@ describe("ArgumentFieldVisitor", () => {
289
289
 
290
290
  expect(field.type).toBe("variant")
291
291
  expect(field.label).toBe("status")
292
- expect(field.options).toEqual(["Inactive", "Active", "Pending"])
292
+ expect(field.options.map((f) => f.label)).toEqual([
293
+ "Inactive",
294
+ "Active",
295
+ "Pending",
296
+ ])
293
297
  expect(field.defaultOption).toBe("Inactive")
294
- expect(field.fields).toHaveLength(3)
295
- expect(field.optionMap.has("Active")).toBe(true)
296
-
297
- field.fields.forEach((f) => {
298
- expect(f.type).toBe("null")
299
- })
298
+ expect(field.options).toHaveLength(3)
299
+ expect(field.options.some((f) => f.label === "Active")).toBe(true)
300
300
 
301
301
  // Test getOptionDefault helper
302
302
  expect(field.getOptionDefault("Active")).toEqual({ _type: "Active" })
@@ -338,9 +338,13 @@ describe("ArgumentFieldVisitor", () => {
338
338
  )
339
339
 
340
340
  expect(field.type).toBe("variant")
341
- expect(field.options).toEqual(["Approve", "Burn", "Transfer"]) // Sorted order
341
+ expect(field.options.map((f) => f.label)).toEqual([
342
+ "Approve",
343
+ "Burn",
344
+ "Transfer",
345
+ ]) // Sorted order
342
346
 
343
- const transferField = field.fields.find(
347
+ const transferField = field.options.find(
344
348
  (f) => f.label === "Transfer"
345
349
  ) as RecordField
346
350
  if (!transferField || transferField.type !== "record") {
@@ -349,7 +353,7 @@ describe("ArgumentFieldVisitor", () => {
349
353
  expect(transferField.type).toBe("record")
350
354
  expect(transferField.fields).toHaveLength(2)
351
355
 
352
- const burnField = field.fields.find((f) => f.label === "Burn")
356
+ const burnField = field.options.find((f) => f.label === "Burn")
353
357
  if (!burnField || burnField.type !== "text") {
354
358
  throw new Error("Burn field not found or not text")
355
359
  }
@@ -371,16 +375,16 @@ describe("ArgumentFieldVisitor", () => {
371
375
  )
372
376
 
373
377
  expect(field.type).toBe("variant")
374
- expect(field.options).toContain("Ok")
375
- expect(field.options).toContain("Err")
378
+ expect(field.options.some((f) => f.label === "Ok")).toBe(true)
379
+ expect(field.options.some((f) => f.label === "Err")).toBe(true)
376
380
 
377
- const okField = field.fields.find((f) => f.label === "Ok")
381
+ const okField = field.options.find((f) => f.label === "Ok")
378
382
  if (!okField || okField.type !== "text") {
379
383
  throw new Error("Ok field not found or not text")
380
384
  }
381
385
  expect(okField.type).toBe("text")
382
386
 
383
- const errField = field.fields.find((f) => f.label === "Err")
387
+ const errField = field.options.find((f) => f.label === "Err")
384
388
  if (!errField || errField.type !== "text") {
385
389
  throw new Error("Err field not found or not text")
386
390
  }
@@ -567,8 +571,8 @@ describe("ArgumentFieldVisitor", () => {
567
571
  throw new Error("Extracted field is not variant")
568
572
  }
569
573
  expect(extracted.type).toBe("variant")
570
- expect(extracted.options).toContain("Leaf")
571
- expect(extracted.options).toContain("Node")
574
+ expect(extracted.options.some((f) => f.label === "Leaf")).toBe(true)
575
+ expect(extracted.options.some((f) => f.label === "Node")).toBe(true)
572
576
  })
573
577
 
574
578
  it("should handle recursive linked list", () => {
@@ -602,9 +606,9 @@ describe("ArgumentFieldVisitor", () => {
602
606
  throw new Error("Extracted field is not variant")
603
607
  }
604
608
  expect(extracted.type).toBe("variant")
605
- expect(extracted.options).toEqual(["Nil", "Cons"])
609
+ expect(extracted.options.map((f) => f.label)).toEqual(["Nil", "Cons"])
606
610
 
607
- const consField = extracted.fields.find(
611
+ const consField = extracted.options.find(
608
612
  (f) => f.label === "Cons"
609
613
  ) as RecordField
610
614
  if (!consField || consField.type !== "record") {
@@ -626,11 +630,11 @@ describe("ArgumentFieldVisitor", () => {
626
630
 
627
631
  expect(meta.functionType).toBe("query")
628
632
  expect(meta.functionName).toBe("lookup")
629
- expect(meta.fields).toHaveLength(1)
630
- expect(meta.fields[0].type).toBe("text")
631
- expect(meta.defaultValues).toEqual([""])
633
+ expect(meta.args).toHaveLength(1)
634
+ expect(meta.args[0].type).toBe("text")
635
+ expect(meta.defaults).toEqual([""])
632
636
  expect(meta.argCount).toBe(1)
633
- expect(meta.isNoArgs).toBe(false)
637
+ expect(meta.isEmpty).toBe(false)
634
638
  })
635
639
 
636
640
  it("should handle update function", () => {
@@ -653,10 +657,10 @@ describe("ArgumentFieldVisitor", () => {
653
657
 
654
658
  expect(meta.functionType).toBe("update")
655
659
  expect(meta.functionName).toBe("transfer")
656
- expect(meta.fields).toHaveLength(1)
657
- expect(meta.fields[0].type).toBe("record")
660
+ expect(meta.args).toHaveLength(1)
661
+ expect(meta.args[0].type).toBe("record")
658
662
 
659
- const recordField = meta.fields[0] as RecordField
663
+ const recordField = meta.args[0] as RecordField
660
664
  if (recordField.type !== "record") {
661
665
  throw new Error("Expected record field")
662
666
  }
@@ -671,10 +675,10 @@ describe("ArgumentFieldVisitor", () => {
671
675
  )
672
676
  const meta = visitor.visitFunc(funcType, "authorize")
673
677
 
674
- expect(meta.fields).toHaveLength(3)
675
- expect(meta.fields[0].type).toBe("principal")
676
- expect(meta.fields[1].type).toBe("text")
677
- expect(meta.fields[2].type).toBe("optional")
678
+ expect(meta.args).toHaveLength(3)
679
+ expect(meta.args[0].type).toBe("principal")
680
+ expect(meta.args[1].type).toBe("text")
681
+ expect(meta.args[2].type).toBe("optional")
678
682
  expect(meta.argCount).toBe(3)
679
683
  })
680
684
 
@@ -683,10 +687,10 @@ describe("ArgumentFieldVisitor", () => {
683
687
  const meta = visitor.visitFunc(funcType, "getBalance")
684
688
 
685
689
  expect(meta.functionType).toBe("query")
686
- expect(meta.fields).toHaveLength(0)
687
- expect(meta.defaultValues).toEqual([])
690
+ expect(meta.args).toHaveLength(0)
691
+ expect(meta.defaults).toEqual([])
688
692
  expect(meta.argCount).toBe(0)
689
- expect(meta.isNoArgs).toBe(true)
693
+ expect(meta.isEmpty).toBe(true)
690
694
  })
691
695
  })
692
696
 
@@ -730,20 +734,20 @@ describe("ArgumentFieldVisitor", () => {
730
734
  // Check get_balance
731
735
  const getBalanceMeta = serviceMeta["get_balance"]
732
736
  expect(getBalanceMeta.functionType).toBe("query")
733
- expect(getBalanceMeta.fields).toHaveLength(1)
734
- expect(getBalanceMeta.fields[0].type).toBe("principal")
737
+ expect(getBalanceMeta.args).toHaveLength(1)
738
+ expect(getBalanceMeta.args[0].type).toBe("principal")
735
739
 
736
740
  // Check transfer
737
741
  const transferMeta = serviceMeta["transfer"]
738
742
  expect(transferMeta.functionType).toBe("update")
739
- expect(transferMeta.fields).toHaveLength(1)
740
- expect(transferMeta.fields[0].type).toBe("record")
743
+ expect(transferMeta.args).toHaveLength(1)
744
+ expect(transferMeta.args[0].type).toBe("record")
741
745
 
742
746
  // Check get_metadata
743
747
  const getMetadataMeta = serviceMeta["get_metadata"]
744
748
  expect(getMetadataMeta.functionType).toBe("query")
745
- expect(getMetadataMeta.fields).toHaveLength(0)
746
- expect(getMetadataMeta.isNoArgs).toBe(true)
749
+ expect(getMetadataMeta.args).toHaveLength(0)
750
+ expect(getMetadataMeta.isEmpty).toBe(true)
747
751
  })
748
752
  })
749
753
 
@@ -768,7 +772,7 @@ describe("ArgumentFieldVisitor", () => {
768
772
  )
769
773
  const meta = visitor.visitFunc(funcType, "updateUser")
770
774
 
771
- const argRecord = meta.fields[0] as RecordField
775
+ const argRecord = meta.args[0] as RecordField
772
776
  if (argRecord.type !== "record") {
773
777
  throw new Error("Expected record field")
774
778
  }
@@ -799,7 +803,7 @@ describe("ArgumentFieldVisitor", () => {
799
803
  const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
800
804
  const meta = visitor.visitFunc(funcType, "addTags")
801
805
 
802
- const vecField = meta.fields[0] as VectorField
806
+ const vecField = meta.args[0] as VectorField
803
807
  if (vecField.type !== "vector") {
804
808
  throw new Error("Expected vector field")
805
809
  }
@@ -919,12 +923,16 @@ describe("ArgumentFieldVisitor", () => {
919
923
  )
920
924
 
921
925
  expect(field.type).toBe("variant")
922
- expect(field.options).toContain("Motion")
923
- expect(field.options).toContain("TransferSnsTreasuryFunds")
924
- expect(field.options).toContain("UpgradeSnsControlledCanister")
926
+ expect(field.options.some((f) => f.label === "Motion")).toBe(true)
927
+ expect(
928
+ field.options.some((f) => f.label === "TransferSnsTreasuryFunds")
929
+ ).toBe(true)
930
+ expect(
931
+ field.options.some((f) => f.label === "UpgradeSnsControlledCanister")
932
+ ).toBe(true)
925
933
 
926
934
  // Check Motion variant
927
- const motionField = field.fields.find(
935
+ const motionField = field.options.find(
928
936
  (f) => f.label === "Motion"
929
937
  ) as RecordField
930
938
  if (!motionField || motionField.type !== "record") {
@@ -934,7 +942,7 @@ describe("ArgumentFieldVisitor", () => {
934
942
  expect(motionField.fields).toHaveLength(1)
935
943
 
936
944
  // Check TransferSnsTreasuryFunds variant
937
- const transferField = field.fields.find(
945
+ const transferField = field.options.find(
938
946
  (f) => f.label === "TransferSnsTreasuryFunds"
939
947
  ) as RecordField
940
948
  if (!transferField || transferField.type !== "record") {
@@ -1003,8 +1011,8 @@ describe("ArgumentFieldVisitor", () => {
1003
1011
  const funcType = IDL.Func([IDL.Text, IDL.Nat], [], [])
1004
1012
  const meta = visitor.visitFunc(funcType, "test")
1005
1013
 
1006
- expect(meta.fields[0].displayLabel).toBe("Arg 0")
1007
- expect(meta.fields[1].displayLabel).toBe("Arg 1")
1014
+ expect(meta.args[0].displayLabel).toBe("Arg 0")
1015
+ expect(meta.args[1].displayLabel).toBe("Arg 1")
1008
1016
  })
1009
1017
 
1010
1018
  it("should format tuple index labels correctly", () => {
@@ -1223,14 +1231,14 @@ describe("ArgumentFieldVisitor", () => {
1223
1231
  "action"
1224
1232
  )
1225
1233
 
1226
- const transferField = field.getField("Transfer")
1234
+ const transferField = field.getOption("Transfer")
1227
1235
  expect(transferField.type).toBe("record")
1228
1236
 
1229
- const burnField = field.getField("Burn")
1237
+ const burnField = field.getOption("Burn")
1230
1238
  expect(burnField.type).toBe("text") // nat is rendered as text for large numbers
1231
1239
  })
1232
1240
 
1233
- it("getSelectedOption should return the selected option from a value", () => {
1241
+ it("getSelectedKey should return the selected key from a value", () => {
1234
1242
  const variantType = IDL.Variant({ A: IDL.Null, B: IDL.Text, C: IDL.Nat })
1235
1243
  const field = visitor.visitVariant(
1236
1244
  variantType,
@@ -1242,11 +1250,11 @@ describe("ArgumentFieldVisitor", () => {
1242
1250
  "choice"
1243
1251
  )
1244
1252
 
1245
- expect(field.getSelectedOption({ A: null })).toBe("A")
1246
- expect(field.getSelectedOption({ B: "hello" })).toBe("B")
1247
- expect(field.getSelectedOption({ C: "100" })).toBe("C")
1253
+ expect(field.getSelectedKey({ A: null })).toBe("A")
1254
+ expect(field.getSelectedKey({ B: "hello" })).toBe("B")
1255
+ expect(field.getSelectedKey({ C: "100" })).toBe("C")
1248
1256
  // Falls back to default option for unknown values
1249
- expect(field.getSelectedOption({})).toBe("A")
1257
+ expect(field.getSelectedKey({})).toBe("A")
1250
1258
  })
1251
1259
 
1252
1260
  it("getSelectedField should return the field for the selected option", () => {
@@ -1263,10 +1271,10 @@ describe("ArgumentFieldVisitor", () => {
1263
1271
  "result"
1264
1272
  )
1265
1273
 
1266
- const okField = field.getSelectedField({ Ok: "100" })
1274
+ const okField = field.getSelectedOption({ Ok: "100" })
1267
1275
  expect(okField.label).toBe("Ok")
1268
1276
 
1269
- const errField = field.getSelectedField({ Err: "error" })
1277
+ const errField = field.getSelectedOption({ Err: "error" })
1270
1278
  expect(errField.label).toBe("Err")
1271
1279
  })
1272
1280
  })
@@ -1304,7 +1312,7 @@ describe("ArgumentFieldVisitor", () => {
1304
1312
  it("should create item field with correct index in name path", () => {
1305
1313
  const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
1306
1314
  const meta = visitor.visitFunc(funcType, "addItems")
1307
- const vecField = meta.fields[0] as VectorField
1315
+ const vecField = meta.args[0] as VectorField
1308
1316
 
1309
1317
  const item0 = vecField.createItemField(0)
1310
1318
  expect(item0.name).toBe("[0][0]")
@@ -1320,7 +1328,7 @@ describe("ArgumentFieldVisitor", () => {
1320
1328
  []
1321
1329
  )
1322
1330
  const meta = visitor.visitFunc(funcType, "addItems")
1323
- const vecField = meta.fields[0] as VectorField
1331
+ const vecField = meta.args[0] as VectorField
1324
1332
 
1325
1333
  const item = vecField.createItemField(3, { label: "Person 3" })
1326
1334
  expect(item.label).toBe("Person 3")
@@ -1330,7 +1338,7 @@ describe("ArgumentFieldVisitor", () => {
1330
1338
  it("should use default label when not provided", () => {
1331
1339
  const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
1332
1340
  const meta = visitor.visitFunc(funcType, "addTags")
1333
- const vecField = meta.fields[0] as VectorField
1341
+ const vecField = meta.args[0] as VectorField
1334
1342
 
1335
1343
  const item = vecField.createItemField(2)
1336
1344
  expect(item.label).toBe("Item 2")
@@ -1392,4 +1400,40 @@ describe("ArgumentFieldVisitor", () => {
1392
1400
  }
1393
1401
  })
1394
1402
  })
1403
+
1404
+ // ════════════════════════════════════════════════════════════════════════
1405
+ // Format Detection
1406
+ // ════════════════════════════════════════════════════════════════════════
1407
+
1408
+ describe("Format Detection", () => {
1409
+ it("should NOT detect eth format for method_name", () => {
1410
+ const field = visitor.visitText(IDL.Text, "method_name")
1411
+ expect(field.format).toBe("plain")
1412
+ })
1413
+
1414
+ it("should detect eth format for my_eth_address", () => {
1415
+ const field = visitor.visitText(IDL.Text, "my_eth_address")
1416
+ expect(field.format).toBe("eth")
1417
+ })
1418
+
1419
+ it("should detect eth format for myEthAddress", () => {
1420
+ const field = visitor.visitText(IDL.Text, "myEthAddress")
1421
+ expect(field.format).toBe("eth")
1422
+ })
1423
+
1424
+ it("should detect eth format for ethereum", () => {
1425
+ const field = visitor.visitText(IDL.Text, "ethereum")
1426
+ expect(field.format).toBe("eth")
1427
+ })
1428
+
1429
+ it("should detect btc format for bitcoin_address", () => {
1430
+ const field = visitor.visitText(IDL.Text, "bitcoin_address")
1431
+ expect(field.format).toBe("btc")
1432
+ })
1433
+
1434
+ it("should NOT detect btc format for debt", () => {
1435
+ const field = visitor.visitText(IDL.Text, "debt")
1436
+ expect(field.format).toBe("plain")
1437
+ })
1438
+ })
1395
1439
  })
@@ -1,5 +1,6 @@
1
1
  import { isQuery } from "../helpers"
2
2
  import { checkTextFormat, checkNumberFormat } from "../constants"
3
+ import { MetadataError } from "./types"
3
4
  import type {
4
5
  FieldNode,
5
6
  RecordField,
@@ -233,13 +234,13 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
233
234
  const functionType = isQuery(t) ? "query" : "update"
234
235
  const argCount = t.argTypes.length
235
236
 
236
- const fields = t.argTypes.map((arg, index) => {
237
+ const args = t.argTypes.map((arg, index) => {
237
238
  return this.withName(`[${index}]`, () =>
238
239
  arg.accept(this, `__arg${index}`)
239
240
  ) as FieldNode
240
241
  })
241
242
 
242
- const defaultValues = fields.map((field) => field.defaultValue)
243
+ const defaults = args.map((field) => field.defaultValue)
243
244
 
244
245
  // Handle empty args case for schema
245
246
  // For no-arg functions, use an empty array schema
@@ -250,7 +251,7 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
250
251
  [z.ZodTypeAny, ...z.ZodTypeAny[]]
251
252
  >)
252
253
  : z.tuple(
253
- fields.map((field) => field.schema) as [
254
+ args.map((field) => field.schema) as [
254
255
  z.ZodTypeAny,
255
256
  ...z.ZodTypeAny[],
256
257
  ]
@@ -259,11 +260,11 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
259
260
  return {
260
261
  functionType,
261
262
  functionName,
262
- fields,
263
- defaultValues: defaultValues,
263
+ args,
264
+ defaults,
264
265
  schema,
265
266
  argCount,
266
- isNoArgs: argCount === 0,
267
+ isEmpty: argCount === 0,
267
268
  }
268
269
  }
269
270
 
@@ -278,7 +279,6 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
278
279
  ): RecordField {
279
280
  const name = this.currentName()
280
281
  const fields: FieldNode[] = []
281
- const fieldMap = new Map<string, FieldNode>()
282
282
  const defaultValue: Record<string, unknown> = {}
283
283
  const schemaShape: Record<string, z.ZodTypeAny> = {}
284
284
 
@@ -288,7 +288,6 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
288
288
  ) as FieldNode
289
289
 
290
290
  fields.push(field)
291
- fieldMap.set(key, field)
292
291
  defaultValue[key] = field.defaultValue
293
292
  schemaShape[key] = field.schema
294
293
  }
@@ -303,7 +302,6 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
303
302
  component: "record-container",
304
303
  renderHint: COMPOUND_RENDER_HINT,
305
304
  fields,
306
- fieldMap,
307
305
  defaultValue,
308
306
  schema,
309
307
  candidType: "record",
@@ -316,9 +314,7 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
316
314
  label: string
317
315
  ): VariantField {
318
316
  const name = this.currentName()
319
- const fields: FieldNode[] = []
320
- const options: string[] = []
321
- const optionMap = new Map<string, FieldNode>()
317
+ const options: FieldNode[] = []
322
318
  const variantSchemas: z.ZodTypeAny[] = []
323
319
 
324
320
  for (const [key, type] of fields_) {
@@ -326,9 +322,7 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
326
322
  type.accept(this, key)
327
323
  ) as FieldNode
328
324
 
329
- fields.push(field)
330
- options.push(key)
331
- optionMap.set(key, field)
325
+ options.push(field)
332
326
 
333
327
  if (field.type === "null") {
334
328
  variantSchemas.push(z.object({ _type: z.literal(key) }))
@@ -342,24 +336,28 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
342
336
  }
343
337
  }
344
338
 
345
- const defaultOption = options[0]
346
- const firstField = fields[0]
339
+ const firstOption = options[0]
340
+ const defaultOption = firstOption.label
347
341
 
348
342
  const defaultValue =
349
- firstField.type === "null"
343
+ firstOption.type === "null"
350
344
  ? { _type: defaultOption }
351
345
  : {
352
346
  _type: defaultOption,
353
- [defaultOption]: firstField.defaultValue,
347
+ [defaultOption]: firstOption.defaultValue,
354
348
  }
355
349
 
356
- const schema = z.union(variantSchemas)
350
+ const schema = z.union(variantSchemas as [z.ZodTypeAny, ...z.ZodTypeAny[]])
357
351
 
358
352
  // Helper to get default value for any option
359
353
  const getOptionDefault = (option: string): Record<string, unknown> => {
360
- const optField = optionMap.get(option)
354
+ const optField = options.find((f) => f.label === option)
361
355
  if (!optField) {
362
- throw new Error(`Unknown variant option: ${option}`)
356
+ throw new MetadataError(
357
+ `Unknown variant option: "${option}". Available: ${options.map((o) => o.label).join(", ")}`,
358
+ name,
359
+ "variant"
360
+ )
363
361
  }
364
362
  return optField.type === "null"
365
363
  ? { _type: option }
@@ -367,27 +365,33 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
367
365
  }
368
366
 
369
367
  // Helper to get field for a specific option
370
- const getField = (option: string): FieldNode => {
371
- const optField = optionMap.get(option)
368
+ const getOption = (option: string): FieldNode => {
369
+ const optField = options.find((f) => f.label === option)
372
370
  if (!optField) {
373
- throw new Error(`Unknown variant option: ${option}`)
371
+ throw new MetadataError(
372
+ `Unknown variant option: "${option}". Available: ${options.map((o) => o.label).join(", ")}`,
373
+ name,
374
+ "variant"
375
+ )
374
376
  }
375
377
  return optField
376
378
  }
377
379
 
378
- // Helper to get currently selected option from a value
379
- const getSelectedOption = (value: Record<string, unknown>): string => {
380
+ // Helper to get currently selected option key from a value
381
+ const getSelectedKey = (value: Record<string, unknown>): string => {
380
382
  if (value._type && typeof value._type === "string") {
381
383
  return value._type
382
384
  }
383
- const validKeys = Object.keys(value).filter((k) => options.includes(k))
385
+ const validKeys = Object.keys(value).filter((k) =>
386
+ options.some((f) => f.label === k)
387
+ )
384
388
  return validKeys[0] ?? defaultOption
385
389
  }
386
390
 
387
- // Helper to get selected field from a value
388
- const getSelectedField = (value: Record<string, unknown>): FieldNode => {
389
- const selectedOption = getSelectedOption(value)
390
- return getField(selectedOption)
391
+ // Helper to get the field for the currently selected option
392
+ const getSelectedOption = (value: Record<string, unknown>): FieldNode => {
393
+ const selectedKey = getSelectedKey(value)
394
+ return getOption(selectedKey)
391
395
  }
392
396
 
393
397
  return {
@@ -397,16 +401,14 @@ export class FieldVisitor<A = BaseActor> extends IDL.Visitor<
397
401
  name,
398
402
  component: "variant-select",
399
403
  renderHint: COMPOUND_RENDER_HINT,
400
- fields,
401
404
  options,
402
405
  defaultOption,
403
- optionMap,
404
406
  defaultValue,
405
407
  schema,
406
408
  getOptionDefault,
407
- getField,
409
+ getOption,
410
+ getSelectedKey,
408
411
  getSelectedOption,
409
- getSelectedField,
410
412
  candidType: "variant",
411
413
  }
412
414
  }