@ic-reactor/candid 3.0.8-beta.2 → 3.0.10-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,23 @@
1
1
  import { describe, it, expect } from "vitest"
2
- import { IDL } from "@icp-sdk/core/candid"
2
+ import { IDL } from "../types"
3
3
  import { ResultFieldVisitor } from "./index"
4
4
  import type {
5
- ResultField,
6
- RecordResultField,
7
- VariantResultField,
8
- TupleResultField,
9
- OptionalResultField,
10
- VectorResultField,
11
- BlobResultField,
12
- RecursiveResultField,
13
- NumberResultField,
14
- TextResultField,
15
- PrincipalResultField,
16
- BooleanResultField,
17
- NullResultField,
18
- MethodResultMeta,
19
- ServiceResultMeta,
5
+ ResultNode,
6
+ ResolvedNode,
7
+ RecordNode,
8
+ VariantNode,
9
+ TupleNode,
10
+ OptionalNode,
11
+ VectorNode,
12
+ BlobNode,
13
+ RecursiveNode,
14
+ NumberNode,
15
+ TextNode,
16
+ PrincipalNode,
17
+ BooleanNode,
18
+ NullNode,
19
+ MethodMeta,
20
+ ServiceMeta,
20
21
  } from "./types"
21
22
 
22
23
  describe("ResultFieldVisitor", () => {
@@ -35,33 +36,33 @@ describe("ResultFieldVisitor", () => {
35
36
  expect(field.label).toBe("message")
36
37
  expect(field.candidType).toBe("text")
37
38
  expect(field.displayType).toBe("string")
38
- expect(field.textFormat).toBe("plain")
39
+ expect(field.format).toBe("plain")
39
40
  })
40
41
 
41
42
  it("should handle text with special label detection", () => {
42
43
  const emailField = visitor.visitText(IDL.Text, "email")
43
- expect(emailField.textFormat).toBe("email")
44
+ expect(emailField.format).toBe("email")
44
45
 
45
46
  const urlField = visitor.visitText(IDL.Text, "website_url")
46
- expect(urlField.textFormat).toBe("url")
47
+ expect(urlField.format).toBe("url")
47
48
 
48
49
  const phoneField = visitor.visitText(IDL.Text, "phone_number")
49
- expect(phoneField.textFormat).toBe("phone")
50
+ expect(phoneField.format).toBe("phone")
50
51
 
51
52
  const uuidField = visitor.visitText(IDL.Text, "transaction_uuid")
52
- expect(uuidField.textFormat).toBe("uuid")
53
+ expect(uuidField.format).toBe("uuid")
53
54
 
54
55
  const btcField = visitor.visitText(IDL.Text, "btc_address")
55
- expect(btcField.textFormat).toBe("btc")
56
+ expect(btcField.format).toBe("btc")
56
57
 
57
58
  const ethField = visitor.visitText(IDL.Text, "ethereum_address")
58
- expect(ethField.textFormat).toBe("eth")
59
+ expect(ethField.format).toBe("eth")
59
60
 
60
61
  const accountIdField = visitor.visitText(IDL.Text, "account_id")
61
- expect(accountIdField.textFormat).toBe("account-id")
62
+ expect(accountIdField.format).toBe("account-id")
62
63
 
63
64
  const principalField = visitor.visitText(IDL.Text, "canister_id")
64
- expect(principalField.textFormat).toBe("principal")
65
+ expect(principalField.format).toBe("principal")
65
66
  })
66
67
 
67
68
  it("should handle bool type", () => {
@@ -106,7 +107,7 @@ describe("ResultFieldVisitor", () => {
106
107
  expect(field.type).toBe("number")
107
108
  expect(field.candidType).toBe("nat")
108
109
  expect(field.displayType).toBe("string") // BigInt → string
109
- expect(field.numberFormat).toBe("normal")
110
+ expect(field.format).toBe("normal")
110
111
  })
111
112
 
112
113
  it("should map int to string display type", () => {
@@ -191,30 +192,30 @@ describe("ResultFieldVisitor", () => {
191
192
  describe("Number format detection", () => {
192
193
  it("should detect timestamp format from label", () => {
193
194
  const timestampField = visitor.visitFixedNat(IDL.Nat64, "created_at")
194
- expect(timestampField.numberFormat).toBe("timestamp")
195
+ expect(timestampField.format).toBe("timestamp")
195
196
 
196
197
  const dateField = visitor.visitNat(IDL.Nat, "timestamp_nanos")
197
- expect(dateField.numberFormat).toBe("timestamp")
198
+ expect(dateField.format).toBe("timestamp")
198
199
 
199
200
  const deadlineField = visitor.visitNat(IDL.Nat, "deadline")
200
- expect(deadlineField.numberFormat).toBe("timestamp")
201
+ expect(deadlineField.format).toBe("timestamp")
201
202
  })
202
203
 
203
204
  it("should detect cycle format from label", () => {
204
205
  const cycleField = visitor.visitNat(IDL.Nat, "cycles")
205
- expect(cycleField.numberFormat).toBe("cycle")
206
+ expect(cycleField.format).toBe("cycle")
206
207
 
207
208
  // "cycle" as standalone word
208
209
  const cycleSingleField = visitor.visitNat(IDL.Nat, "cycle")
209
- expect(cycleSingleField.numberFormat).toBe("cycle")
210
+ expect(cycleSingleField.format).toBe("cycle")
210
211
  })
211
212
 
212
213
  it("should default to normal format", () => {
213
214
  const amountField = visitor.visitNat(IDL.Nat, "amount")
214
- expect(amountField.numberFormat).toBe("normal")
215
+ expect(amountField.format).toBe("normal")
215
216
 
216
217
  const countField = visitor.visitNat(IDL.Nat, "count")
217
- expect(countField.numberFormat).toBe("normal")
218
+ expect(countField.format).toBe("normal")
218
219
  })
219
220
  })
220
221
  })
@@ -242,15 +243,15 @@ describe("ResultFieldVisitor", () => {
242
243
  expect(field.label).toBe("person")
243
244
  expect(field.candidType).toBe("record")
244
245
  expect(field.displayType).toBe("object")
245
- expect(field.fields).toHaveLength(2)
246
+ expect(Object.keys(field.fields)).toHaveLength(2)
246
247
 
247
- const nameField = field.fields.find((f) => f.label === "name")
248
+ const nameField = field.fields["name"]
248
249
  if (!nameField || nameField.type !== "text") {
249
250
  throw new Error("Name field not found or not text")
250
251
  }
251
252
  expect(nameField.displayType).toBe("string")
252
253
 
253
- const ageField = field.fields.find((f) => f.label === "age")
254
+ const ageField = field.fields["age"]
254
255
  if (!ageField || ageField.type !== "number") {
255
256
  throw new Error("Age field not found or not number")
256
257
  }
@@ -277,14 +278,14 @@ describe("ResultFieldVisitor", () => {
277
278
 
278
279
  expect(field.type).toBe("record")
279
280
 
280
- const addressField = field.fields.find((f) => f.label === "address")
281
+ const addressField = field.fields["address"] as RecordNode
281
282
  if (!addressField || addressField.type !== "record") {
282
283
  throw new Error("Address field not found or not a record")
283
284
  }
284
285
 
285
286
  expect(addressField.type).toBe("record")
286
287
  expect(addressField.displayType).toBe("object")
287
- expect(addressField.fields).toHaveLength(2)
288
+ expect(Object.keys(addressField.fields)).toHaveLength(2)
288
289
  })
289
290
 
290
291
  it("should handle ICRC-1 account record", () => {
@@ -303,22 +304,23 @@ describe("ResultFieldVisitor", () => {
303
304
 
304
305
  expect(field.type).toBe("record")
305
306
 
306
- const ownerField = field.fields.find((f) => f.label === "owner")
307
+ const ownerField = field.fields["owner"]
307
308
  if (!ownerField || ownerField.type !== "principal") {
308
309
  throw new Error("Owner field not found or not principal")
309
310
  }
310
311
  expect(ownerField.type).toBe("principal")
311
312
  expect(ownerField.displayType).toBe("string")
312
313
 
313
- const subaccountField = field.fields.find((f) => f.label === "subaccount")
314
+ const subaccountField = field.fields["subaccount"] as OptionalNode
314
315
  if (!subaccountField || subaccountField.type !== "optional") {
315
316
  throw new Error("Subaccount field not found or not optional")
316
317
  }
317
318
  expect(subaccountField.type).toBe("optional")
318
- if (subaccountField.innerField.type !== "blob") {
319
- throw new Error("Subaccount inner field is not blob")
320
- }
321
- expect(subaccountField.innerField.type).toBe("blob")
319
+ // Resolve an example value to validate inner blob type
320
+ const subaccountResolved = subaccountField.resolve([
321
+ new Uint8Array([1, 2, 3]),
322
+ ])
323
+ expect((subaccountResolved.value as ResolvedNode).type).toBe("blob")
322
324
  })
323
325
  })
324
326
 
@@ -342,10 +344,14 @@ describe("ResultFieldVisitor", () => {
342
344
  expect(field.type).toBe("variant")
343
345
  expect(field.candidType).toBe("variant")
344
346
  expect(field.displayType).toBe("variant")
345
- expect(field.options).toContain("Active")
346
- expect(field.options).toContain("Inactive")
347
- expect(field.options).toContain("Pending")
348
- expect(field.options).toHaveLength(3)
347
+ // Validate options by resolving each option
348
+ const resolvedActive = field.resolve({ Active: null })
349
+ expect(resolvedActive.selected).toBe("Active")
350
+ expect(resolvedActive.selectedOption.type).toBe("null")
351
+ const resolvedInactive = field.resolve({ Inactive: null })
352
+ expect(resolvedInactive.selected).toBe("Inactive")
353
+ const resolvedPending = field.resolve({ Pending: null })
354
+ expect(resolvedPending.selected).toBe("Pending")
349
355
  })
350
356
 
351
357
  it("should handle variant with payloads", () => {
@@ -385,26 +391,19 @@ describe("ResultFieldVisitor", () => {
385
391
  )
386
392
 
387
393
  expect(field.type).toBe("variant")
388
- expect(field.options).toContain("Transfer")
389
- expect(field.options).toContain("Approve")
390
- expect(field.options).toContain("Mint")
391
- expect(field.options).toHaveLength(3)
392
-
393
- const transferField = field.optionFields.find(
394
- (f) => f.label === "Transfer"
395
- )
396
-
397
- if (!transferField || transferField.type !== "record") {
398
- throw new Error("Transfer field not found or not record")
399
- }
400
- expect(transferField.type).toBe("record")
401
- expect(transferField.fields).toHaveLength(3)
394
+ // Validate Transfer payload
395
+ const transferResolved = field.resolve({
396
+ Transfer: { from: "aaaaa-aa", to: "bbbbb-bb", amount: BigInt(1) },
397
+ })
398
+ expect(transferResolved.selected).toBe("Transfer")
399
+ expect(transferResolved.selectedOption.type).toBe("record")
400
+ expect(
401
+ Object.keys((transferResolved.selectedOption as RecordNode).fields)
402
+ ).toHaveLength(3)
402
403
 
403
- const approveField = field.optionFields.find((f) => f.label === "Approve")
404
- if (!approveField || approveField.type !== "number") {
405
- throw new Error("Approve field not found or not number")
406
- }
407
- expect(approveField.type).toBe("number")
404
+ const approveResolved = field.resolve({ Approve: BigInt(5) })
405
+ expect(approveResolved.selected).toBe("Approve")
406
+ expect(approveResolved.selectedOption.type).toBe("number")
408
407
  })
409
408
 
410
409
  it("should detect Result variant (Ok/Err)", () => {
@@ -423,20 +422,16 @@ describe("ResultFieldVisitor", () => {
423
422
 
424
423
  expect(field.type).toBe("variant")
425
424
  expect(field.displayType).toBe("result") // Special result type
426
- expect(field.options).toContain("Ok")
427
- expect(field.options).toContain("Err")
428
425
 
429
- const okField = field.optionFields.find((f) => f.label === "Ok")
430
- if (!okField || okField.type !== "number") {
431
- throw new Error("Ok field not found or not number")
432
- }
433
- expect(okField.displayType).toBe("string")
426
+ const okResolved = field.resolve({ Ok: BigInt(1) })
427
+ expect(okResolved.selected).toBe("Ok")
428
+ expect(okResolved.selectedOption.type).toBe("number")
429
+ expect(okResolved.selectedOption.displayType).toBe("string")
434
430
 
435
- const errField = field.optionFields.find((f) => f.label === "Err")
436
- if (!errField || errField.type !== "text") {
437
- throw new Error("Err field not found or not text")
438
- }
439
- expect(errField.displayType).toBe("string")
431
+ const errResolved = field.resolve({ Err: "error" })
432
+ expect(errResolved.selected).toBe("Err")
433
+ expect(errResolved.selectedOption.type).toBe("text")
434
+ expect(errResolved.selectedOption.displayType).toBe("string")
440
435
  })
441
436
 
442
437
  it("should detect complex Result variant", () => {
@@ -475,17 +470,15 @@ describe("ResultFieldVisitor", () => {
475
470
 
476
471
  expect(field.displayType).toBe("result")
477
472
 
478
- const okField = field.optionFields.find((f) => f.label === "Ok")
479
- if (!okField || okField.type !== "record") {
480
- throw new Error("Ok field not found or not record")
481
- }
482
- expect(okField.type).toBe("record")
473
+ const okResolved = field.resolve({
474
+ Ok: { id: BigInt(1), data: new Uint8Array([1, 2, 3]) },
475
+ })
476
+ expect(okResolved.selected).toBe("Ok")
477
+ expect(okResolved.selectedOption.type).toBe("record")
483
478
 
484
- const errField = field.optionFields.find((f) => f.label === "Err")
485
- if (!errField || errField.type !== "variant") {
486
- throw new Error("Err field not found or not variant")
487
- }
488
- expect(errField.type).toBe("variant")
479
+ const errResolved = field.resolve({ Err: { NotFound: null } })
480
+ expect(errResolved.selected).toBe("Err")
481
+ expect(errResolved.selectedOption.type).toBe("variant")
489
482
  })
490
483
 
491
484
  it("should not detect non-Result variant with Ok and other options", () => {
@@ -516,9 +509,9 @@ describe("ResultFieldVisitor", () => {
516
509
  expect(field.type).toBe("tuple")
517
510
  expect(field.candidType).toBe("tuple")
518
511
  expect(field.displayType).toBe("array")
519
- expect(field.fields).toHaveLength(2)
520
- expect(field.fields[0].type).toBe("text")
521
- expect(field.fields[1].type).toBe("number")
512
+ expect(field.items).toHaveLength(2)
513
+ expect(field.items[0].type).toBe("text")
514
+ expect(field.items[1].type).toBe("number")
522
515
  })
523
516
 
524
517
  it("should handle tuple with mixed types", () => {
@@ -534,17 +527,17 @@ describe("ResultFieldVisitor", () => {
534
527
  "data"
535
528
  )
536
529
 
537
- expect(field.fields).toHaveLength(4)
538
- expect(field.fields[0].type).toBe("principal")
539
- expect(field.fields[1].type).toBe("number")
540
- const numField = field.fields[1]
530
+ expect(field.items).toHaveLength(4)
531
+ expect(field.items[0].type).toBe("principal")
532
+ expect(field.items[1].type).toBe("number")
533
+ const numField = field.items[1]
541
534
  if (numField.type === "number") {
542
535
  expect(numField.displayType).toBe("string") // nat64 → string
543
536
  } else {
544
537
  throw new Error("Expected number field")
545
538
  }
546
- expect(field.fields[2].type).toBe("boolean")
547
- expect(field.fields[3].type).toBe("blob")
539
+ expect(field.items[2].type).toBe("boolean")
540
+ expect(field.items[3].type).toBe("blob")
548
541
  })
549
542
  })
550
543
 
@@ -556,7 +549,9 @@ describe("ResultFieldVisitor", () => {
556
549
  expect(field.type).toBe("optional")
557
550
  expect(field.candidType).toBe("opt")
558
551
  expect(field.displayType).toBe("nullable") // opt T → T | null
559
- expect(field.innerField.type).toBe("text")
552
+ // Validate inner by resolving a value
553
+ const optResolved = field.resolve(["Bob"])
554
+ expect((optResolved.value as ResolvedNode).type).toBe("text")
560
555
  })
561
556
 
562
557
  it("should handle optional record", () => {
@@ -568,7 +563,9 @@ describe("ResultFieldVisitor", () => {
568
563
  const field = visitor.visitOpt(optType, recordInOpt, "metadata")
569
564
 
570
565
  expect(field.type).toBe("optional")
571
- expect(field.innerField.type).toBe("record")
566
+ // Validate inner by resolving a record
567
+ const metaResolved = field.resolve([{ name: "x", value: BigInt(1) }])
568
+ expect((metaResolved.value as ResolvedNode).type).toBe("record")
572
569
  })
573
570
 
574
571
  it("should handle nested optional", () => {
@@ -577,13 +574,11 @@ describe("ResultFieldVisitor", () => {
577
574
  const field = visitor.visitOpt(optType, innerOpt, "maybeNumber")
578
575
 
579
576
  expect(field.type).toBe("optional")
580
- expect(field.innerField.type).toBe("optional")
581
- const inner = field.innerField
582
- if (inner.type === "optional") {
583
- expect(inner.innerField.type).toBe("number")
584
- } else {
585
- throw new Error("Inner field is not optional")
586
- }
577
+ // Validate nested optional by resolving a nested array
578
+ const nestedResolved = field.resolve([[BigInt(1)]])
579
+ expect((nestedResolved.value as ResolvedNode).type).toBe("optional")
580
+ const innerResolved = nestedResolved.value as OptionalNode
581
+ expect((innerResolved.value as ResolvedNode).type).toBe("number")
587
582
  })
588
583
  })
589
584
 
@@ -595,11 +590,10 @@ describe("ResultFieldVisitor", () => {
595
590
  expect(field.type).toBe("vector")
596
591
  expect(field.candidType).toBe("vec")
597
592
  expect(field.displayType).toBe("array")
598
- if (field.type === "vector") {
599
- expect(field.itemField.type).toBe("text")
600
- } else {
601
- throw new Error("Field is not a vector")
602
- }
593
+ // Validate items by resolving
594
+ const vecResolved = field.resolve(["a", "b", "c"]) as VectorNode
595
+ expect(vecResolved.items).toHaveLength(3)
596
+ expect(vecResolved.items[0].value).toBe("a")
603
597
  })
604
598
 
605
599
  it("should handle vector of records", () => {
@@ -611,11 +605,11 @@ describe("ResultFieldVisitor", () => {
611
605
  const field = visitor.visitVec(vecType, recType, "items")
612
606
 
613
607
  expect(field.type).toBe("vector")
614
- if (field.type === "vector") {
615
- expect(field.itemField.type).toBe("record")
616
- } else {
617
- throw new Error("Field is not a vector")
618
- }
608
+ // Validate by resolving
609
+ const itemsResolved = field.resolve([
610
+ { id: BigInt(1), name: "x" },
611
+ ]) as VectorNode
612
+ expect(itemsResolved.items[0].type).toBe("record")
619
613
  })
620
614
 
621
615
  it("should handle blob (vec nat8)", () => {
@@ -625,11 +619,11 @@ describe("ResultFieldVisitor", () => {
625
619
  expect(field.type).toBe("blob")
626
620
  expect(field.candidType).toBe("blob")
627
621
  expect(field.displayType).toBe("string") // Blob → hex string
628
- if (field.type === "blob") {
629
- expect(field.displayHint).toBe("hex")
630
- } else {
631
- throw new Error("Field is not a blob")
632
- }
622
+ // Validate by resolving a Uint8Array
623
+ const blobResolved = field.resolve(
624
+ new Uint8Array([0x12, 0x34, 0xab, 0xcd])
625
+ )
626
+ expect(blobResolved.value).toBe("1234abcd")
633
627
  })
634
628
 
635
629
  it("should handle nested vectors", () => {
@@ -638,11 +632,9 @@ describe("ResultFieldVisitor", () => {
638
632
  const field = visitor.visitVec(nestedVecType, innerVec, "matrix")
639
633
 
640
634
  expect(field.type).toBe("vector")
641
- if (field.type === "vector") {
642
- expect(field.itemField.type).toBe("vector")
643
- } else {
644
- throw new Error("Field is not a vector")
645
- }
635
+ // Validate nested items via resolve
636
+ const nestedResolved = field.resolve([[BigInt(1)]]) as VectorNode
637
+ expect(nestedResolved.items[0].type).toBe("vector")
646
638
  })
647
639
 
648
640
  it("should handle vec of tuples (Map-like)", () => {
@@ -652,13 +644,15 @@ describe("ResultFieldVisitor", () => {
652
644
  mapType,
653
645
  tupleType,
654
646
  "metadata"
655
- ) as VectorResultField
647
+ ) as VectorNode
656
648
 
657
649
  expect(field.type).toBe("vector")
658
- expect(field.itemField.type).toBe("tuple")
659
- const itemField = field.itemField
660
- if (itemField.type === "tuple") {
661
- expect(itemField.fields).toHaveLength(2)
650
+ // Validate by resolving a tuple item
651
+ const vecTupleResolved = field.resolve([["a", BigInt(1)]])
652
+ expect(vecTupleResolved.items[0].type).toBe("tuple")
653
+ const item = vecTupleResolved.items[0] as TupleNode
654
+ if (item.type === "tuple") {
655
+ expect(item.items).toHaveLength(2)
662
656
  } else {
663
657
  throw new Error("Item field is not tuple")
664
658
  }
@@ -693,17 +687,10 @@ describe("ResultFieldVisitor", () => {
693
687
  expect(field.type).toBe("recursive")
694
688
  expect(field.candidType).toBe("rec")
695
689
  expect(field.displayType).toBe("recursive")
696
- expect(field.typeName).toBeDefined()
697
- expect(typeof field.extract).toBe("function")
698
-
699
- // Extract should return the variant
700
- const extracted = field.extract()
701
- if (extracted.type !== "variant") {
702
- throw new Error("Extracted field is not variant")
703
- }
704
- expect(extracted.type).toBe("variant")
705
- expect(extracted.options).toContain("Leaf")
706
- expect(extracted.options).toContain("Node")
690
+ // Resolve to inspect inner schema
691
+ const treeResolved = field.resolve({ Leaf: BigInt(1) }) as RecursiveNode
692
+ expect(treeResolved.inner.type).toBe("variant")
693
+ expect((treeResolved.inner as VariantNode).selected).toBe("Leaf")
707
694
  })
708
695
 
709
696
  it("should handle recursive linked list", () => {
@@ -732,11 +719,9 @@ describe("ResultFieldVisitor", () => {
732
719
 
733
720
  expect(field.type).toBe("recursive")
734
721
 
735
- const extracted = field.extract()
736
- if (extracted.type !== "variant") {
737
- throw new Error("Extracted field is not variant")
738
- }
739
- expect(extracted.options).toEqual(["Nil", "Cons"])
722
+ const listResolved = field.resolve({ Nil: null }) as RecursiveNode
723
+ expect(listResolved.inner.type).toBe("variant")
724
+ expect((listResolved.inner as VariantNode).selected).toBe("Nil")
740
725
  })
741
726
  })
742
727
 
@@ -752,11 +737,11 @@ describe("ResultFieldVisitor", () => {
752
737
  expect(meta.functionType).toBe("query")
753
738
  expect(meta.functionName).toBe("get_balance")
754
739
  expect(meta.returnCount).toBe(1)
755
- expect(meta.resultFields).toHaveLength(1)
740
+ expect(meta.returns).toHaveLength(1)
756
741
 
757
- expect(meta.resultFields).toHaveLength(1)
742
+ expect(meta.returns).toHaveLength(1)
758
743
 
759
- const returnField = meta.resultFields[0]
744
+ const returnField = meta.returns[0]
760
745
  if (returnField.type === "number") {
761
746
  expect(returnField.candidType).toBe("nat")
762
747
  expect(returnField.displayType).toBe("string")
@@ -786,7 +771,7 @@ describe("ResultFieldVisitor", () => {
786
771
  expect(meta.functionType).toBe("update")
787
772
  expect(meta.returnCount).toBe(1)
788
773
 
789
- const resultField = meta.resultFields[0]
774
+ const resultField = meta.returns[0]
790
775
  if (resultField.type === "variant") {
791
776
  expect(resultField.displayType).toBe("result")
792
777
  } else {
@@ -799,10 +784,10 @@ describe("ResultFieldVisitor", () => {
799
784
  const meta = visitor.visitFunc(funcType, "get_info")
800
785
 
801
786
  expect(meta.returnCount).toBe(3)
802
- expect(meta.resultFields).toHaveLength(3)
803
- expect(meta.resultFields[0].type).toBe("text")
804
- expect(meta.resultFields[1].type).toBe("number")
805
- expect(meta.resultFields[2].type).toBe("boolean")
787
+ expect(meta.returns).toHaveLength(3)
788
+ expect(meta.returns[0].type).toBe("text")
789
+ expect(meta.returns[1].type).toBe("number")
790
+ expect(meta.returns[2].type).toBe("boolean")
806
791
  })
807
792
 
808
793
  it("should handle function with no returns", () => {
@@ -810,7 +795,7 @@ describe("ResultFieldVisitor", () => {
810
795
  const meta = visitor.visitFunc(funcType, "log")
811
796
 
812
797
  expect(meta.returnCount).toBe(0)
813
- expect(meta.resultFields).toHaveLength(0)
798
+ expect(meta.returns).toHaveLength(0)
814
799
  })
815
800
  })
816
801
 
@@ -852,8 +837,8 @@ describe("ResultFieldVisitor", () => {
852
837
  const getBalanceMeta = serviceMeta["get_balance"]
853
838
  expect(getBalanceMeta.functionType).toBe("query")
854
839
  expect(getBalanceMeta.returnCount).toBe(1)
855
- expect(getBalanceMeta.resultFields[0].type).toBe("number")
856
- const balanceField = getBalanceMeta.resultFields[0]
840
+ expect(getBalanceMeta.returns[0].type).toBe("number")
841
+ const balanceField = getBalanceMeta.returns[0]
857
842
  if (balanceField.type === "number") {
858
843
  expect(balanceField.displayType).toBe("string")
859
844
  } else {
@@ -867,7 +852,7 @@ describe("ResultFieldVisitor", () => {
867
852
  // Check get_metadata
868
853
  const getMetadataMeta = serviceMeta["get_metadata"]
869
854
  expect(getMetadataMeta.returnCount).toBe(1)
870
- expect(getMetadataMeta.resultFields[0].type).toBe("vector")
855
+ expect(getMetadataMeta.returns[0].type).toBe("vector")
871
856
  })
872
857
  })
873
858
 
@@ -892,7 +877,7 @@ describe("ResultFieldVisitor", () => {
892
877
  expect(meta.functionType).toBe("query")
893
878
  expect(meta.returnCount).toBe(1)
894
879
 
895
- const balanceField = meta.resultFields[0]
880
+ const balanceField = meta.returns[0]
896
881
  if (balanceField.type === "number") {
897
882
  expect(balanceField.candidType).toBe("nat")
898
883
  expect(balanceField.displayType).toBe("string")
@@ -942,23 +927,28 @@ describe("ResultFieldVisitor", () => {
942
927
 
943
928
  expect(field.displayType).toBe("result")
944
929
 
945
- const okField = field.optionFields.find((f) => f.label === "Ok")
946
-
947
- if (!okField || okField.type !== "number") {
930
+ const okResolved = field.resolve({ Ok: BigInt(123) })
931
+ if ((okResolved.selectedOption as ResolvedNode).type !== "number") {
948
932
  throw new Error("Ok field is not number")
949
933
  }
934
+ expect((okResolved.selectedOption as ResolvedNode).candidType).toBe("nat")
935
+ expect((okResolved.selectedOption as ResolvedNode).displayType).toBe(
936
+ "string"
937
+ )
950
938
 
951
- expect(okField.candidType).toBe("nat")
952
- expect(okField.displayType).toBe("string")
953
-
954
- const errField = field.optionFields.find((f) => f.label === "Err")
955
- if (!errField || errField.type !== "variant") {
956
- throw new Error("Err field is not variant")
957
- }
958
-
959
- expect(errField.type).toBe("variant")
960
- expect(errField.options).toContain("InsufficientFunds")
961
- expect(errField.options).toContain("GenericError")
939
+ const errResolved = field.resolve({
940
+ Err: { InsufficientFunds: { balance: BigInt(0) } },
941
+ })
942
+ const innerErr = errResolved.selectedOption as ResolvedNode
943
+ expect(innerErr.type).toBe("variant")
944
+ const insufficient = (innerErr as any).resolve({
945
+ InsufficientFunds: { balance: BigInt(0) },
946
+ })
947
+ expect(insufficient.selected).toBe("InsufficientFunds")
948
+ const generic = (innerErr as any).resolve({
949
+ GenericError: { error_code: BigInt(1), message: "err" },
950
+ })
951
+ expect(generic.selected).toBe("GenericError")
962
952
  })
963
953
 
964
954
  it("should handle SNS canister status return", () => {
@@ -1011,45 +1001,46 @@ describe("ResultFieldVisitor", () => {
1011
1001
  expect(field.type).toBe("record")
1012
1002
 
1013
1003
  // Check status variant
1014
- const statusField = field.fields.find((f) => f.label === "status")
1004
+ const statusField = field.fields["status"] as VariantNode
1015
1005
  if (!statusField || statusField.type !== "variant") {
1016
- throw new Error("Status field is not variant")
1006
+ throw new Error("Status field not found or not variant")
1017
1007
  }
1018
1008
  expect(statusField.type).toBe("variant")
1019
- expect(statusField.options).toContain("running")
1020
- expect(statusField.options).toContain("stopping")
1021
- expect(statusField.options).toContain("stopped")
1022
- expect(statusField.options).toHaveLength(3)
1009
+ expect(statusField.displayType).toBe("variant")
1010
+ // Validate via resolving options
1011
+ const statusResolved = statusField.resolve({ running: null })
1012
+ expect(statusResolved.selected).toBe("running")
1023
1013
 
1024
1014
  // Check settings record
1025
- const settingsField = field.fields.find((f) => f.label === "settings")
1015
+ const settingsField = field.fields["settings"] as RecordNode
1026
1016
  if (!settingsField || settingsField.type !== "record") {
1027
- throw new Error("Settings field is not record")
1017
+ throw new Error("Settings field not found or not record")
1028
1018
  }
1029
1019
  expect(settingsField.type).toBe("record")
1020
+ expect(settingsField.displayType).toBe("object")
1030
1021
 
1031
- const controllersField = settingsField.fields.find(
1032
- (f) => f.label === "controllers"
1033
- )
1022
+ const controllersField = settingsField.fields["controllers"] as VectorNode
1034
1023
  if (!controllersField || controllersField.type !== "vector") {
1035
- throw new Error("Controllers field is not vector")
1024
+ throw new Error("Controllers field not found or not vector")
1036
1025
  }
1037
1026
  expect(controllersField.type).toBe("vector")
1038
- expect(controllersField.itemField.type).toBe("principal")
1027
+ // Validate item type via resolve
1028
+ const controllersResolved = controllersField.resolve(["aaaaa-aa"])
1029
+ expect(controllersResolved.items[0].type).toBe("principal")
1039
1030
 
1040
1031
  // Check cycles - should detect special format
1041
- const cyclesField = field.fields.find((f) => f.label === "cycles")
1032
+ const cyclesField = field.fields["cycles"] as NumberNode
1042
1033
  if (!cyclesField || cyclesField.type !== "number") {
1043
- throw new Error("Cycles field is not number")
1034
+ throw new Error("Cycles field not found or not number")
1044
1035
  }
1045
- expect(cyclesField.numberFormat).toBe("cycle")
1036
+ expect(cyclesField.format).toBe("cycle")
1046
1037
 
1047
1038
  // Check module_hash - optional blob
1048
- const moduleHashField = field.fields.find(
1049
- (f) => f.label === "module_hash"
1050
- ) as OptionalResultField
1039
+ const moduleHashField = field.fields["module_hash"] as OptionalNode
1051
1040
  expect(moduleHashField.type).toBe("optional")
1052
- expect(moduleHashField.innerField.type).toBe("blob")
1041
+ // Validate via resolving a sample blob
1042
+ const moduleHashResolved = moduleHashField.resolve([new Uint8Array([1])])
1043
+ expect((moduleHashResolved.value as ResolvedNode).type).toBe("blob")
1053
1044
  })
1054
1045
 
1055
1046
  it("should handle complex governance proposal types", () => {
@@ -1114,12 +1105,10 @@ describe("ResultFieldVisitor", () => {
1114
1105
  )
1115
1106
 
1116
1107
  expect(field.type).toBe("record")
1117
- expect(field.fields.length).toBeGreaterThan(8)
1108
+ expect(Object.keys(field.fields)).toHaveLength(12)
1118
1109
 
1119
1110
  // Check timestamp field (note: the label pattern matching may not match "proposal_timestamp_seconds")
1120
- const timestampField = field.fields.find(
1121
- (f) => f.label === "proposal_timestamp_seconds"
1122
- )
1111
+ const timestampField = field.fields["proposal_timestamp_seconds"]
1123
1112
  if (!timestampField || timestampField.type !== "number") {
1124
1113
  throw new Error("Timestamp field not found or not number")
1125
1114
  }
@@ -1127,20 +1116,23 @@ describe("ResultFieldVisitor", () => {
1127
1116
  expect(timestampField.displayType).toBe("string") // nat64 → string
1128
1117
 
1129
1118
  // Check ballots - vec of tuples
1130
- const ballotsField = field.fields.find((f) => f.label === "ballots")
1119
+ const ballotsField = field.fields["ballots"]
1131
1120
  if (!ballotsField || ballotsField.type !== "vector") {
1132
1121
  throw new Error("Ballots field not found or not vector")
1133
1122
  }
1134
1123
  expect(ballotsField.type).toBe("vector")
1135
- expect(ballotsField.itemField.type).toBe("tuple")
1136
-
1137
- const ballotTuple = ballotsField.itemField
1124
+ // Validate via resolve
1125
+ const ballotsResolved = ballotsField.resolve([
1126
+ [BigInt(1), { vote: 1, voting_power: BigInt(2) }],
1127
+ ]) as TupleNode
1128
+ expect(ballotsResolved.items).toHaveLength(1)
1129
+ const ballotTuple = ballotsResolved.items[0] as TupleNode
1138
1130
  if (ballotTuple.type !== "tuple") {
1139
1131
  throw new Error("Ballot item is not tuple")
1140
1132
  }
1141
- expect(ballotTuple.fields).toHaveLength(2)
1142
- expect(ballotTuple.fields[0].type).toBe("number") // nat64
1143
- expect(ballotTuple.fields[1].type).toBe("record") // ballot record
1133
+ expect(ballotTuple.items).toHaveLength(2)
1134
+ expect(ballotTuple.items[0].type).toBe("number") // nat64
1135
+ expect(ballotTuple.items[1].type).toBe("record") // ballot record
1144
1136
  })
1145
1137
  })
1146
1138
 
@@ -1179,7 +1171,7 @@ describe("ResultFieldVisitor", () => {
1179
1171
  if (typeof field === "object" && "displayType" in field) {
1180
1172
  expect(field.displayType).toBe(expectedDisplay)
1181
1173
  } else {
1182
- throw new Error("Expected a ResultField")
1174
+ throw new Error("Expected as ResultNode")
1183
1175
  }
1184
1176
  })
1185
1177
  })
@@ -1194,40 +1186,30 @@ describe("ResultFieldVisitor", () => {
1194
1186
  it("should resolve text field with value", () => {
1195
1187
  const field = visitor.visitText(IDL.Text, "message")
1196
1188
  const resolved = field.resolve("Hello World")
1197
-
1198
- expect(resolved.field).toBe(field)
1199
1189
  expect(resolved.value).toBe("Hello World")
1200
1190
  })
1201
1191
 
1202
1192
  it("should resolve number field with value", () => {
1203
1193
  const field = visitor.visitNat(IDL.Nat, "amount")
1204
1194
  const resolved = field.resolve(BigInt(1000000))
1205
-
1206
- expect(resolved.field).toBe(field)
1207
1195
  expect(resolved.value).toBe("1000000")
1208
1196
  })
1209
1197
 
1210
1198
  it("should resolve boolean field with value", () => {
1211
1199
  const field = visitor.visitBool(IDL.Bool, "active")
1212
1200
  const resolved = field.resolve(true)
1213
-
1214
- expect(resolved.field).toBe(field)
1215
1201
  expect(resolved.value).toBe(true)
1216
1202
  })
1217
1203
 
1218
1204
  it("should resolve null field", () => {
1219
1205
  const field = visitor.visitNull(IDL.Null, "empty")
1220
1206
  const resolved = field.resolve(null)
1221
-
1222
- expect(resolved.field).toBe(field)
1223
1207
  expect(resolved.value).toBe(null)
1224
1208
  })
1225
1209
 
1226
1210
  it("should resolve principal field with string value", () => {
1227
1211
  const field = visitor.visitPrincipal(IDL.Principal, "owner")
1228
1212
  const resolved = field.resolve("aaaaa-aa")
1229
-
1230
- expect(resolved.field).toBe(field)
1231
1213
  expect(resolved.value).toBe("aaaaa-aa")
1232
1214
  })
1233
1215
  })
@@ -1255,14 +1237,11 @@ describe("ResultFieldVisitor", () => {
1255
1237
  active: true,
1256
1238
  })
1257
1239
 
1258
- expect(resolved.field).toBe(field)
1259
- const value = resolved.value as Record<
1260
- string,
1261
- { field: ResultField; value: unknown }
1262
- >
1263
- expect(value.name.value).toBe("Alice")
1264
- expect(value.age.value).toBe(30)
1265
- expect(value.active.value).toBe(true)
1240
+ expect(resolved.type).toBe(field.type)
1241
+ const value = resolved.fields as Record<string, ResolvedNode>
1242
+ expect(value["name"].value).toBe("Alice")
1243
+ expect(value["age"].value).toBe(30)
1244
+ expect(value["active"].value).toBe(true)
1266
1245
  })
1267
1246
 
1268
1247
  it("should handle null record value", () => {
@@ -1273,8 +1252,9 @@ describe("ResultFieldVisitor", () => {
1273
1252
  "user"
1274
1253
  )
1275
1254
 
1276
- const resolved = field.resolve(null)
1277
- expect(resolved.value).toBe(null)
1255
+ expect(() => field.resolve(null)).toThrow(
1256
+ "Expected record for field user, but got null"
1257
+ )
1278
1258
  })
1279
1259
  })
1280
1260
 
@@ -1294,13 +1274,10 @@ describe("ResultFieldVisitor", () => {
1294
1274
  )
1295
1275
 
1296
1276
  const resolved = field.resolve({ Ok: "Success" })
1297
-
1298
- const value = resolved.value as {
1299
- option: string
1300
- value: { field: ResultField; value: unknown }
1301
- }
1302
- expect(value.option).toBe("Ok")
1303
- expect(value.value.value).toBe("Success")
1277
+ expect(resolved.type).toBe(field.type)
1278
+ expect(resolved.selected).toBe("Ok")
1279
+ const data = resolved.selectedOption as ResolvedNode
1280
+ expect(data.value).toBe("Success")
1304
1281
  })
1305
1282
 
1306
1283
  it("should resolve variant with Err option", () => {
@@ -1319,12 +1296,9 @@ describe("ResultFieldVisitor", () => {
1319
1296
 
1320
1297
  const resolved = field.resolve({ Err: "Something went wrong" })
1321
1298
 
1322
- const value = resolved.value as {
1323
- option: string
1324
- value: { field: ResultField; value: unknown }
1325
- }
1326
- expect(value.option).toBe("Err")
1327
- expect(value.value.value).toBe("Something went wrong")
1299
+ expect(resolved.selected).toBe("Err")
1300
+ const data = resolved.selectedOption as ResolvedNode
1301
+ expect(data.value).toBe("Something went wrong")
1328
1302
  })
1329
1303
 
1330
1304
  it("should handle null variant value", () => {
@@ -1338,8 +1312,9 @@ describe("ResultFieldVisitor", () => {
1338
1312
  "choice"
1339
1313
  )
1340
1314
 
1341
- const resolved = field.resolve(null)
1342
- expect(resolved.value).toBe(null)
1315
+ expect(() => field.resolve(null)).toThrow(
1316
+ "Expected variant for field choice, but got null"
1317
+ )
1343
1318
  })
1344
1319
  })
1345
1320
 
@@ -1354,11 +1329,8 @@ describe("ResultFieldVisitor", () => {
1354
1329
 
1355
1330
  const resolved = field.resolve(["hello", 123n, true])
1356
1331
 
1357
- expect(resolved.field).toBe(field)
1358
- const value = resolved.value as Array<{
1359
- field: ResultField
1360
- value: unknown
1361
- }>
1332
+ expect(resolved.type).toBe(field.type)
1333
+ const value = resolved.items as ResolvedNode[]
1362
1334
  expect(value).toHaveLength(3)
1363
1335
  expect(value[0].value).toBe("hello")
1364
1336
  expect(value[1].value).toBe("123")
@@ -1369,8 +1341,9 @@ describe("ResultFieldVisitor", () => {
1369
1341
  const tupleType = IDL.Tuple(IDL.Text, IDL.Nat)
1370
1342
  const field = visitor.visitTuple(tupleType, [IDL.Text, IDL.Nat], "pair")
1371
1343
 
1372
- const resolved = field.resolve(null)
1373
- expect(resolved.value).toBe(null)
1344
+ expect(() => field.resolve(null)).toThrow(
1345
+ "Expected tuple for field pair, but got null"
1346
+ )
1374
1347
  })
1375
1348
  })
1376
1349
 
@@ -1381,8 +1354,8 @@ describe("ResultFieldVisitor", () => {
1381
1354
 
1382
1355
  const resolved = field.resolve(["Bob"])
1383
1356
 
1384
- expect(resolved.field).toBe(field)
1385
- const inner = resolved.value as { field: ResultField; value: unknown }
1357
+ expect(resolved.type).toBe(field.type)
1358
+ const inner = resolved.value as ResolvedNode
1386
1359
  expect(inner.value).toBe("Bob")
1387
1360
  })
1388
1361
 
@@ -1391,8 +1364,6 @@ describe("ResultFieldVisitor", () => {
1391
1364
  const field = visitor.visitOpt(optType, IDL.Text, "nickname")
1392
1365
 
1393
1366
  const resolved = field.resolve(null)
1394
-
1395
- expect(resolved.field).toBe(field)
1396
1367
  expect(resolved.value).toBe(null)
1397
1368
  })
1398
1369
  })
@@ -1402,13 +1373,10 @@ describe("ResultFieldVisitor", () => {
1402
1373
  const vecType = IDL.Vec(IDL.Text)
1403
1374
  const field = visitor.visitVec(vecType, IDL.Text, "tags")
1404
1375
 
1405
- const resolved = field.resolve(["a", "b", "c"])
1376
+ const resolved = field.resolve(["a", "b", "c"]) as VectorNode
1406
1377
 
1407
- expect(resolved.field).toBe(field)
1408
- const value = resolved.value as Array<{
1409
- field: ResultField
1410
- value: unknown
1411
- }>
1378
+ expect(resolved.type).toBe(field.type)
1379
+ const value = resolved.items as ResolvedNode[]
1412
1380
  expect(value).toHaveLength(3)
1413
1381
  expect(value[0].value).toBe("a")
1414
1382
  expect(value[1].value).toBe("b")
@@ -1419,12 +1387,11 @@ describe("ResultFieldVisitor", () => {
1419
1387
  const vecType = IDL.Vec(IDL.Nat)
1420
1388
  const field = visitor.visitVec(vecType, IDL.Nat, "numbers")
1421
1389
 
1422
- const resolved = field.resolve([])
1390
+ const resolved = field.resolve([]) as VectorNode
1391
+
1392
+ expect(resolved.type).toBe(field.type)
1423
1393
 
1424
- const value = resolved.value as Array<{
1425
- field: ResultField
1426
- value: unknown
1427
- }>
1394
+ const value = resolved.items as ResolvedNode[]
1428
1395
  expect(value).toHaveLength(0)
1429
1396
  })
1430
1397
 
@@ -1432,8 +1399,9 @@ describe("ResultFieldVisitor", () => {
1432
1399
  const vecType = IDL.Vec(IDL.Text)
1433
1400
  const field = visitor.visitVec(vecType, IDL.Text, "items")
1434
1401
 
1435
- const resolved = field.resolve(null)
1436
- expect(resolved.value).toBe(null)
1402
+ expect(() => field.resolve(null)).toThrow(
1403
+ "Expected vector for field items, but got null"
1404
+ )
1437
1405
  })
1438
1406
  })
1439
1407
 
@@ -1444,7 +1412,7 @@ describe("ResultFieldVisitor", () => {
1444
1412
 
1445
1413
  const resolved = field.resolve(new Uint8Array([0x12, 0x34, 0xab, 0xcd]))
1446
1414
 
1447
- expect(resolved.field).toBe(field)
1415
+ expect(resolved.type).toBe(field.type)
1448
1416
  expect(resolved.value).toBe("1234abcd")
1449
1417
  })
1450
1418
  })
@@ -1475,7 +1443,7 @@ describe("ResultFieldVisitor", () => {
1475
1443
  })
1476
1444
 
1477
1445
  // The recursive type should delegate to its inner record type
1478
- expect(resolved.field.type).toBe("record")
1446
+ expect(resolved.type).toBe("recursive")
1479
1447
  })
1480
1448
  })
1481
1449
 
@@ -1515,29 +1483,24 @@ describe("ResultFieldVisitor", () => {
1515
1483
  },
1516
1484
  })
1517
1485
 
1518
- const value = resolved.value as Record<
1519
- string,
1520
- { field: ResultField; value: unknown }
1521
- >
1522
- const userValue = value.user.value as Record<
1523
- string,
1524
- { field: ResultField; value: unknown }
1525
- >
1526
- const profileValue = userValue.profile.value as Record<
1527
- string,
1528
- { field: ResultField; value: unknown }
1529
- >
1530
- expect(profileValue.name.value).toBe("Alice")
1531
- expect(profileValue.verified.value).toBe(true)
1486
+ const value = resolved.fields as Record<string, ResolvedNode>
1487
+ const userNode = value.user as RecordNode
1488
+ if (userNode.type !== "record") {
1489
+ throw new Error("User node is not a record")
1490
+ }
1491
+ const profileNode = userNode.fields["profile"] as RecordNode
1492
+ const profileFields = profileNode.fields as Record<string, ResolvedNode>
1493
+ expect(profileFields["name"].value).toBe("Alice")
1494
+ expect(profileFields["verified"].value).toBe(true)
1532
1495
  })
1533
1496
  })
1534
1497
  })
1535
1498
 
1536
1499
  // ════════════════════════════════════════════════════════════════════════
1537
- // generateMetadata() Method Tests
1500
+ // resolve() Method Tests
1538
1501
  // ════════════════════════════════════════════════════════════════════════
1539
1502
 
1540
- describe("generateMetadata() Method", () => {
1503
+ describe("resolve() Method", () => {
1541
1504
  it("should generate metadata for single return value", () => {
1542
1505
  const service = IDL.Service({
1543
1506
  getName: IDL.Func([], [IDL.Text], ["query"]),
@@ -1548,13 +1511,13 @@ describe("ResultFieldVisitor", () => {
1548
1511
  )
1549
1512
  const methodMeta = serviceMeta["getName"]
1550
1513
 
1551
- const result = methodMeta.generateMetadata("Alice")
1514
+ const result = methodMeta.resolve("Alice")
1552
1515
 
1553
1516
  expect(result.functionName).toBe("getName")
1554
1517
  expect(result.functionType).toBe("query")
1555
1518
  expect(result.results).toHaveLength(1)
1556
1519
  expect(result.results[0].value).toBe("Alice")
1557
- expect(result.results[0].field.type).toBe("text")
1520
+ expect(result.results[0].type).toBe("text")
1558
1521
  })
1559
1522
 
1560
1523
  it("should generate metadata for multiple return values", () => {
@@ -1567,18 +1530,14 @@ describe("ResultFieldVisitor", () => {
1567
1530
  )
1568
1531
  const methodMeta = serviceMeta["getStats"]
1569
1532
 
1570
- const result = methodMeta.generateMetadata([
1571
- BigInt(100),
1572
- BigInt(200),
1573
- "active",
1574
- ])
1533
+ const result = methodMeta.resolve([BigInt(100), BigInt(200), "active"])
1575
1534
 
1576
1535
  expect(result.results).toHaveLength(3)
1577
1536
  expect(result.results[0].value).toBe("100")
1578
- expect(result.results[0].field.type).toBe("number")
1537
+ expect(result.results[0].type).toBe("number")
1579
1538
  expect(result.results[1].value).toBe("200")
1580
1539
  expect(result.results[2].value).toBe("active")
1581
- expect(result.results[2].field.type).toBe("text")
1540
+ expect(result.results[2].type).toBe("text")
1582
1541
  })
1583
1542
 
1584
1543
  it("should generate metadata for record return value", () => {
@@ -1595,22 +1554,19 @@ describe("ResultFieldVisitor", () => {
1595
1554
  )
1596
1555
  const methodMeta = serviceMeta["getUser"]
1597
1556
 
1598
- const result = methodMeta.generateMetadata({
1557
+ const result = methodMeta.resolve({
1599
1558
  name: "Bob",
1600
1559
  balance: BigInt(1000),
1601
1560
  })
1602
1561
 
1603
1562
  expect(result.results).toHaveLength(1)
1604
- expect(result.results[0].field.type).toBe("record")
1563
+ expect(result.results[0].type).toBe("record")
1605
1564
 
1606
- const recordValue = result.results[0].value
1607
- if (typeof recordValue !== "object" || recordValue === null) {
1608
- throw new Error("Expected record value object")
1565
+ const recordNode = result.results[0] as RecordNode
1566
+ if (recordNode.type !== "record") {
1567
+ throw new Error("Expected record node")
1609
1568
  }
1610
- const val = recordValue as Record<
1611
- string,
1612
- { field: ResultField; value: unknown }
1613
- >
1569
+ const val = recordNode.fields as Record<string, ResolvedNode>
1614
1570
  expect(val.name.value).toBe("Bob")
1615
1571
  expect(val.balance.value).toBe("1000")
1616
1572
  })
@@ -1630,31 +1586,29 @@ describe("ResultFieldVisitor", () => {
1630
1586
  const methodMeta = serviceMeta["transfer"]
1631
1587
 
1632
1588
  // Test Ok case
1633
- const okResult = methodMeta.generateMetadata({ Ok: BigInt(12345) })
1634
- expect(okResult.results[0].field.type).toBe("variant")
1635
- if (okResult.results[0].field.type === "variant") {
1636
- expect(okResult.results[0].field.displayType).toBe("result")
1589
+ const okResult = methodMeta.resolve({ Ok: BigInt(12345) })
1590
+ expect(okResult.results[0].type).toBe("variant")
1591
+ if (okResult.results[0].type === "variant") {
1592
+ expect(okResult.results[0].displayType).toBe("result")
1637
1593
  } else {
1638
1594
  throw new Error("Expected variant field")
1639
1595
  }
1640
1596
 
1641
- const okValue = okResult.results[0].value as {
1642
- option: string
1643
- value: { field: ResultField; value: unknown }
1644
- }
1645
- expect(okValue.option).toBe("Ok")
1646
- expect(okValue.value.value).toBe("12345")
1597
+ const okValue = okResult.results[0] as ResolvedNode
1598
+ expect((okValue as any).selected).toBe("Ok")
1599
+ expect(((okValue as any).selectedOption as ResolvedNode).value).toBe(
1600
+ "12345"
1601
+ )
1647
1602
 
1648
1603
  // Test Err case
1649
- const errResult = methodMeta.generateMetadata({
1604
+ const errResult = methodMeta.resolve({
1650
1605
  Err: "Insufficient funds",
1651
1606
  })
1652
- const errValue = errResult.results[0].value as {
1653
- option: string
1654
- value: { field: ResultField; value: unknown }
1655
- }
1656
- expect(errValue.option).toBe("Err")
1657
- expect(errValue.value.value).toBe("Insufficient funds")
1607
+ const errValue = errResult.results[0] as ResolvedNode
1608
+ expect((errValue as any).selected).toBe("Err")
1609
+ expect(((errValue as any).selectedOption as ResolvedNode).value).toBe(
1610
+ "Insufficient funds"
1611
+ )
1658
1612
  })
1659
1613
 
1660
1614
  it("should generate metadata for optional return value", () => {
@@ -1668,16 +1622,13 @@ describe("ResultFieldVisitor", () => {
1668
1622
  const methodMeta = serviceMeta["findUser"]
1669
1623
 
1670
1624
  // Test with value - Optional is [value]
1671
- const foundResult = methodMeta.generateMetadata(["Alice"])
1672
- expect(foundResult.results[0].field.type).toBe("optional")
1673
- const foundInner = foundResult.results[0].value as {
1674
- field: ResultField
1675
- value: unknown
1676
- }
1677
- expect(foundInner.value).toBe("Alice")
1625
+ const foundResult = methodMeta.resolve(["Alice"])
1626
+ expect(foundResult.results[0].type).toBe("optional")
1627
+ const foundInner = foundResult.results[0] as OptionalNode
1628
+ expect(foundInner.value?.value).toBe("Alice")
1678
1629
 
1679
1630
  // Test with null - optional is []
1680
- const notFoundResult = methodMeta.generateMetadata([])
1631
+ const notFoundResult = methodMeta.resolve([])
1681
1632
  expect(notFoundResult.results[0].value).toBe(null)
1682
1633
  })
1683
1634
 
@@ -1691,17 +1642,15 @@ describe("ResultFieldVisitor", () => {
1691
1642
  )
1692
1643
  const methodMeta = serviceMeta["getItems"]
1693
1644
 
1694
- const result = methodMeta.generateMetadata(["item1", "item2", "item3"])
1645
+ const result = methodMeta.resolve(["item1", "item2", "item3"])
1695
1646
 
1696
- expect(result.results[0].field.type).toBe("vector")
1697
- const vecValue = result.results[0].value as Array<{
1698
- field: ResultField
1699
- value: unknown
1700
- }>
1701
- expect(vecValue).toHaveLength(3)
1702
- expect(vecValue[0].value).toBe("item1")
1703
- expect(vecValue[1].value).toBe("item2")
1704
- expect(vecValue[2].value).toBe("item3")
1647
+ expect(result.results[0].type).toBe("vector")
1648
+ const vecNode = result.results[0] as ResolvedNode
1649
+ const vecItems = (vecNode as any).items as ResolvedNode[]
1650
+ expect(vecItems).toHaveLength(3)
1651
+ expect(vecItems[0].value).toBe("item1")
1652
+ expect(vecItems[1].value).toBe("item2")
1653
+ expect(vecItems[2].value).toBe("item3")
1705
1654
  })
1706
1655
 
1707
1656
  it("should generate metadata for update function", () => {
@@ -1717,7 +1666,7 @@ describe("ResultFieldVisitor", () => {
1717
1666
  expect(methodMeta.functionType).toBe("update")
1718
1667
 
1719
1668
  const rawData = [true]
1720
- const result = methodMeta.generateMetadata(rawData[0])
1669
+ const result = methodMeta.resolve(rawData[0])
1721
1670
 
1722
1671
  expect(result.functionType).toBe("update")
1723
1672
  expect(result.functionName).toBe("setName")
@@ -1750,7 +1699,7 @@ describe("ResultFieldVisitor", () => {
1750
1699
  const methodMeta = serviceMeta["icrc1_transfer"]
1751
1700
 
1752
1701
  // Test successful transfer
1753
- const successResult = methodMeta.generateMetadata({ Ok: BigInt(1000) })
1702
+ const successResult = methodMeta.resolve({ Ok: BigInt(1000) })
1754
1703
  console.log(
1755
1704
  "🚀 ~ result:",
1756
1705
  JSON.stringify(
@@ -1760,28 +1709,24 @@ describe("ResultFieldVisitor", () => {
1760
1709
  )
1761
1710
  )
1762
1711
 
1763
- const successValue = successResult.results[0].value as {
1764
- option: string
1765
- value: { field: ResultField; value: unknown }
1766
- }
1767
- expect(successValue.option).toBe("Ok")
1768
- expect(successValue.value.value).toBe("1000")
1712
+ const successValue = successResult.results[0] as ResolvedNode
1713
+ expect((successValue as any).selected).toBe("Ok")
1714
+ expect(((successValue as any).selectedOption as ResolvedNode).value).toBe(
1715
+ "1000"
1716
+ )
1769
1717
 
1770
1718
  // Test error case
1771
- const errorResult = methodMeta.generateMetadata({
1719
+ const errorResult = methodMeta.resolve({
1772
1720
  Err: { InsufficientFunds: { balance: BigInt(50) } },
1773
1721
  })
1774
- const errorValue = errorResult.results[0].value as {
1775
- option: string
1776
- value: { field: ResultField; value: unknown }
1777
- }
1778
- expect(errorValue.option).toBe("Err")
1722
+ const errorValue = errorResult.results[0] as ResolvedNode
1723
+ expect((errorValue as any).selected).toBe("Err")
1779
1724
 
1780
- const val = errorValue.value.value
1781
- if (typeof val !== "object" || val === null || !("option" in val)) {
1725
+ const val = (errorValue as any).selectedOption as ResolvedNode
1726
+ if (typeof val !== "object" || val === null || !("selected" in val)) {
1782
1727
  throw new Error("Expected variant value object")
1783
1728
  }
1784
- expect(val.option).toBe("InsufficientFunds")
1729
+ expect((val as any).selected).toBe("InsufficientFunds")
1785
1730
  })
1786
1731
 
1787
1732
  it("should handle empty return", () => {
@@ -1796,16 +1741,16 @@ describe("ResultFieldVisitor", () => {
1796
1741
 
1797
1742
  expect(methodMeta.returnCount).toBe(0)
1798
1743
 
1799
- const result = methodMeta.generateMetadata([])
1744
+ const result = methodMeta.resolve([])
1800
1745
  expect(result.results).toHaveLength(0)
1801
1746
  })
1802
1747
  })
1803
1748
 
1804
1749
  // ════════════════════════════════════════════════════════════════════════════
1805
- // generateMetadataWithRaw() Method Tests
1750
+ // resolve() Method Tests
1806
1751
  // ════════════════════════════════════════════════════════════════════════════
1807
1752
 
1808
- describe("generateMetadataWithRaw() Method", () => {
1753
+ describe("resolve() Method", () => {
1809
1754
  it("should include both raw and display values for single return", () => {
1810
1755
  const service = IDL.Service({
1811
1756
  getBalance: IDL.Func([], [IDL.Nat], ["query"]),
@@ -1819,7 +1764,7 @@ describe("ResultFieldVisitor", () => {
1819
1764
  // Simulate raw BigInt and display string
1820
1765
  const rawData = [BigInt(1000000)]
1821
1766
 
1822
- const result = methodMeta.generateMetadata(rawData[0])
1767
+ const result = methodMeta.resolve(rawData[0])
1823
1768
 
1824
1769
  expect(result.functionName).toBe("getBalance")
1825
1770
  expect(result.functionType).toBe("query")
@@ -1827,7 +1772,7 @@ describe("ResultFieldVisitor", () => {
1827
1772
  expect(result.results[0].raw).toBe(BigInt(1000000))
1828
1773
  expect(result.results[0].value).toBe("1000000")
1829
1774
  expect(result.results[0].raw).toBe(BigInt(1000000))
1830
- expect(result.results[0].field.type).toBe("number")
1775
+ expect(result.results[0].type).toBe("number")
1831
1776
  })
1832
1777
 
1833
1778
  it("should include both raw and display values for multiple returns", () => {
@@ -1843,14 +1788,14 @@ describe("ResultFieldVisitor", () => {
1843
1788
  // Use BigInt with string to safe safe integer
1844
1789
  const rawData = [BigInt("9007199254740993"), "active", true]
1845
1790
 
1846
- const result = methodMeta.generateMetadata(rawData)
1791
+ const result = methodMeta.resolve(rawData)
1847
1792
 
1848
1793
  expect(result.results).toHaveLength(3)
1849
1794
 
1850
1795
  // nat64 → string display, BigInt raw
1851
1796
  expect(result.results[0].value).toBe("9007199254740993")
1852
1797
  expect(result.results[0].raw).toBe(BigInt("9007199254740993"))
1853
- expect(result.results[0].field.candidType).toBe("nat64")
1798
+ expect(result.results[0].candidType).toBe("nat64")
1854
1799
 
1855
1800
  // text → same for both
1856
1801
  expect(result.results[1].value).toBe("active")
@@ -1876,21 +1821,18 @@ describe("ResultFieldVisitor", () => {
1876
1821
  const methodMeta = serviceMeta["getUser"]
1877
1822
 
1878
1823
  const rawData = [{ name: "Alice", balance: BigInt(500) }]
1879
- const result = methodMeta.generateMetadata(rawData[0])
1824
+ const result = methodMeta.resolve(rawData[0])
1880
1825
 
1881
1826
  expect(result.results[0].raw).toEqual({
1882
1827
  name: "Alice",
1883
1828
  balance: BigInt(500),
1884
1829
  })
1885
1830
 
1886
- const recordValue = result.results[0].value
1887
- if (typeof recordValue !== "object" || recordValue === null) {
1888
- throw new Error("Expected record value object")
1831
+ const recordNode = result.results[0] as RecordNode
1832
+ if (recordNode.type !== "record") {
1833
+ throw new Error("Expected record node")
1889
1834
  }
1890
- const val = recordValue as Record<
1891
- string,
1892
- { field: ResultField; value: unknown }
1893
- >
1835
+ const val = recordNode.fields as Record<string, ResolvedNode>
1894
1836
  expect(val.name.value).toBe("Alice")
1895
1837
  expect(val.balance.value).toBe("500")
1896
1838
  })
@@ -1912,20 +1854,13 @@ describe("ResultFieldVisitor", () => {
1912
1854
  // Test Ok case with raw BigInt
1913
1855
  const rawData = [{ Ok: BigInt(12345) }]
1914
1856
 
1915
- const result = methodMeta.generateMetadata(rawData[0])
1857
+ const result = methodMeta.resolve(rawData[0])
1916
1858
 
1917
1859
  expect(result.results[0].raw).toEqual({ Ok: BigInt(12345) })
1918
1860
 
1919
- const variantValue = result.results[0].value as any
1920
- if (
1921
- typeof variantValue !== "object" ||
1922
- variantValue === null ||
1923
- !("option" in variantValue)
1924
- ) {
1925
- throw new Error("Expected variant value object")
1926
- }
1927
- expect(variantValue.option).toBe("Ok")
1928
- const innerVal = variantValue.value
1861
+ const variantValue = result.results[0] as ResolvedNode
1862
+ expect((variantValue as any).selected).toBe("Ok")
1863
+ const innerVal = (variantValue as any).selectedOption as ResolvedNode
1929
1864
  expect(innerVal.value).toBe("12345")
1930
1865
  })
1931
1866
 
@@ -1944,11 +1879,11 @@ describe("ResultFieldVisitor", () => {
1944
1879
  const principal = Principal.fromText("aaaaa-aa")
1945
1880
  const rawData = [principal]
1946
1881
 
1947
- const result = methodMeta.generateMetadata(rawData[0])
1882
+ const result = methodMeta.resolve(rawData[0])
1948
1883
 
1949
1884
  expect(result.results[0].value).toBe("aaaaa-aa")
1950
1885
  expect(result.results[0].raw).toBe(principal)
1951
- expect(result.results[0].field.type).toBe("principal")
1886
+ expect(result.results[0].type).toBe("principal")
1952
1887
  })
1953
1888
 
1954
1889
  it("should handle vector with raw and display values", () => {
@@ -1963,12 +1898,10 @@ describe("ResultFieldVisitor", () => {
1963
1898
 
1964
1899
  const rawData = [[BigInt(100), BigInt(200), BigInt(300)]]
1965
1900
 
1966
- const result = methodMeta.generateMetadata(rawData[0])
1901
+ const result = methodMeta.resolve(rawData[0])
1967
1902
 
1968
- const vecValue = result.results[0].value
1969
- if (!Array.isArray(vecValue)) {
1970
- throw new Error("Expected vector value array")
1971
- }
1903
+ const vecNode = result.results[0] as ResolvedNode
1904
+ const vecValue = (vecNode as any).items as ResolvedNode[]
1972
1905
  expect(vecValue).toHaveLength(3)
1973
1906
  expect(vecValue[0].value).toBe("100")
1974
1907
  expect(vecValue[1].value).toBe("200")
@@ -1988,7 +1921,7 @@ describe("ResultFieldVisitor", () => {
1988
1921
  // Test with value - Optional is [value]
1989
1922
  const rawDataWithValue = [[BigInt(999)]]
1990
1923
 
1991
- const resultWithValue = methodMeta.generateMetadata(rawDataWithValue[0])
1924
+ const resultWithValue = methodMeta.resolve(rawDataWithValue[0])
1992
1925
 
1993
1926
  expect(resultWithValue.results[0].raw).toEqual([BigInt(999)])
1994
1927
  const innerValue = resultWithValue.results[0].value
@@ -2004,7 +1937,7 @@ describe("ResultFieldVisitor", () => {
2004
1937
  // Test with null - Optional is []
2005
1938
  const rawDataNull = [[]]
2006
1939
 
2007
- const resultNull = methodMeta.generateMetadata(rawDataNull[0])
1940
+ const resultNull = methodMeta.resolve(rawDataNull[0])
2008
1941
  expect(resultNull.results[0].raw).toEqual([])
2009
1942
  expect(resultNull.results[0].value).toBe(null)
2010
1943
  })
@@ -2019,7 +1952,7 @@ describe("ResultFieldVisitor", () => {
2019
1952
  )
2020
1953
  const methodMeta = serviceMeta["doNothing"]
2021
1954
 
2022
- const result = methodMeta.generateMetadata([])
1955
+ const result = methodMeta.resolve([])
2023
1956
 
2024
1957
  expect(result.results).toHaveLength(0)
2025
1958
  })