@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.
- package/dist/metadata-display-reactor.d.ts +66 -11
- package/dist/metadata-display-reactor.d.ts.map +1 -1
- package/dist/metadata-display-reactor.js +48 -12
- package/dist/metadata-display-reactor.js.map +1 -1
- package/dist/visitor/arguments/helpers.d.ts +22 -7
- package/dist/visitor/arguments/helpers.d.ts.map +1 -1
- package/dist/visitor/arguments/helpers.js +39 -5
- package/dist/visitor/arguments/helpers.js.map +1 -1
- package/dist/visitor/arguments/index.d.ts.map +1 -1
- package/dist/visitor/arguments/index.js +26 -34
- package/dist/visitor/arguments/index.js.map +1 -1
- package/dist/visitor/arguments/types.d.ts +120 -132
- package/dist/visitor/arguments/types.d.ts.map +1 -1
- package/dist/visitor/arguments/types.js +25 -1
- package/dist/visitor/arguments/types.js.map +1 -1
- package/dist/visitor/constants.d.ts.map +1 -1
- package/dist/visitor/constants.js +19 -17
- package/dist/visitor/constants.js.map +1 -1
- package/dist/visitor/returns/index.d.ts.map +1 -1
- package/dist/visitor/returns/index.js +24 -13
- package/dist/visitor/returns/index.js.map +1 -1
- package/dist/visitor/returns/types.d.ts +48 -10
- package/dist/visitor/returns/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/metadata-display-reactor.ts +86 -14
- package/src/visitor/arguments/helpers.ts +50 -8
- package/src/visitor/arguments/index.test.ts +105 -61
- package/src/visitor/arguments/index.ts +38 -36
- package/src/visitor/arguments/types.ts +211 -220
- package/src/visitor/constants.ts +24 -15
- package/src/visitor/returns/index.test.ts +30 -30
- package/src/visitor/returns/index.ts +52 -21
- package/src/visitor/returns/types.ts +60 -17
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
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:
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 |
|
|
58
|
-
return
|
|
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.
|
|
152
|
-
expect(field.
|
|
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([
|
|
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.
|
|
295
|
-
expect(field.
|
|
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([
|
|
341
|
+
expect(field.options.map((f) => f.label)).toEqual([
|
|
342
|
+
"Approve",
|
|
343
|
+
"Burn",
|
|
344
|
+
"Transfer",
|
|
345
|
+
]) // Sorted order
|
|
342
346
|
|
|
343
|
-
const transferField = field.
|
|
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.
|
|
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).
|
|
375
|
-
expect(field.options).
|
|
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.
|
|
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.
|
|
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).
|
|
571
|
-
expect(extracted.options).
|
|
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.
|
|
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.
|
|
630
|
-
expect(meta.
|
|
631
|
-
expect(meta.
|
|
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.
|
|
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.
|
|
657
|
-
expect(meta.
|
|
660
|
+
expect(meta.args).toHaveLength(1)
|
|
661
|
+
expect(meta.args[0].type).toBe("record")
|
|
658
662
|
|
|
659
|
-
const recordField = meta.
|
|
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.
|
|
675
|
-
expect(meta.
|
|
676
|
-
expect(meta.
|
|
677
|
-
expect(meta.
|
|
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.
|
|
687
|
-
expect(meta.
|
|
690
|
+
expect(meta.args).toHaveLength(0)
|
|
691
|
+
expect(meta.defaults).toEqual([])
|
|
688
692
|
expect(meta.argCount).toBe(0)
|
|
689
|
-
expect(meta.
|
|
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.
|
|
734
|
-
expect(getBalanceMeta.
|
|
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.
|
|
740
|
-
expect(transferMeta.
|
|
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.
|
|
746
|
-
expect(getMetadataMeta.
|
|
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.
|
|
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.
|
|
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).
|
|
923
|
-
expect(
|
|
924
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1007
|
-
expect(meta.
|
|
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.
|
|
1234
|
+
const transferField = field.getOption("Transfer")
|
|
1227
1235
|
expect(transferField.type).toBe("record")
|
|
1228
1236
|
|
|
1229
|
-
const burnField = field.
|
|
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("
|
|
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.
|
|
1246
|
-
expect(field.
|
|
1247
|
-
expect(field.
|
|
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.
|
|
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.
|
|
1274
|
+
const okField = field.getSelectedOption({ Ok: "100" })
|
|
1267
1275
|
expect(okField.label).toBe("Ok")
|
|
1268
1276
|
|
|
1269
|
-
const errField = field.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
263
|
+
args,
|
|
264
|
+
defaults,
|
|
264
265
|
schema,
|
|
265
266
|
argCount,
|
|
266
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
346
|
-
const
|
|
339
|
+
const firstOption = options[0]
|
|
340
|
+
const defaultOption = firstOption.label
|
|
347
341
|
|
|
348
342
|
const defaultValue =
|
|
349
|
-
|
|
343
|
+
firstOption.type === "null"
|
|
350
344
|
? { _type: defaultOption }
|
|
351
345
|
: {
|
|
352
346
|
_type: defaultOption,
|
|
353
|
-
[defaultOption]:
|
|
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 =
|
|
354
|
+
const optField = options.find((f) => f.label === option)
|
|
361
355
|
if (!optField) {
|
|
362
|
-
throw new
|
|
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
|
|
371
|
-
const optField =
|
|
368
|
+
const getOption = (option: string): FieldNode => {
|
|
369
|
+
const optField = options.find((f) => f.label === option)
|
|
372
370
|
if (!optField) {
|
|
373
|
-
throw new
|
|
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
|
|
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) =>
|
|
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
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
return
|
|
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
|
-
|
|
409
|
+
getOption,
|
|
410
|
+
getSelectedKey,
|
|
408
411
|
getSelectedOption,
|
|
409
|
-
getSelectedField,
|
|
410
412
|
candidType: "variant",
|
|
411
413
|
}
|
|
412
414
|
}
|