@ic-reactor/candid 3.0.11-beta.1 → 3.0.12-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.
Files changed (40) hide show
  1. package/dist/display-reactor.d.ts +1 -2
  2. package/dist/display-reactor.d.ts.map +1 -1
  3. package/dist/display-reactor.js +1 -1
  4. package/dist/display-reactor.js.map +1 -1
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/metadata-display-reactor.d.ts +3 -3
  9. package/dist/metadata-display-reactor.d.ts.map +1 -1
  10. package/dist/visitor/arguments/index.d.ts +58 -39
  11. package/dist/visitor/arguments/index.d.ts.map +1 -1
  12. package/dist/visitor/arguments/index.js +273 -81
  13. package/dist/visitor/arguments/index.js.map +1 -1
  14. package/dist/visitor/arguments/types.d.ts +228 -45
  15. package/dist/visitor/arguments/types.d.ts.map +1 -1
  16. package/dist/visitor/arguments/types.js +40 -1
  17. package/dist/visitor/arguments/types.js.map +1 -1
  18. package/dist/visitor/helpers.d.ts +1 -1
  19. package/dist/visitor/helpers.d.ts.map +1 -1
  20. package/dist/visitor/returns/index.d.ts +3 -3
  21. package/dist/visitor/returns/index.d.ts.map +1 -1
  22. package/dist/visitor/returns/index.js +54 -15
  23. package/dist/visitor/returns/index.js.map +1 -1
  24. package/dist/visitor/types.d.ts +2 -3
  25. package/dist/visitor/types.d.ts.map +1 -1
  26. package/dist/visitor/types.js +1 -2
  27. package/dist/visitor/types.js.map +1 -1
  28. package/package.json +6 -3
  29. package/src/display-reactor.ts +4 -6
  30. package/src/index.ts +0 -1
  31. package/src/metadata-display-reactor.ts +6 -6
  32. package/src/visitor/arguments/README.md +230 -0
  33. package/src/visitor/arguments/index.test.ts +144 -51
  34. package/src/visitor/arguments/index.ts +351 -146
  35. package/src/visitor/arguments/schema.test.ts +215 -0
  36. package/src/visitor/arguments/types.ts +287 -61
  37. package/src/visitor/helpers.ts +1 -1
  38. package/src/visitor/returns/index.test.ts +163 -1
  39. package/src/visitor/returns/index.ts +62 -16
  40. package/src/visitor/types.ts +2 -3
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest"
2
2
  import { IDL } from "@icp-sdk/core/candid"
3
- import { ArgumentFieldVisitor } from "./index"
3
+ import { ArgumentFieldVisitor, VectorField } from "./index"
4
4
 
5
5
  describe("ArgumentFieldVisitor", () => {
6
6
  const visitor = new ArgumentFieldVisitor()
@@ -53,51 +53,70 @@ describe("ArgumentFieldVisitor", () => {
53
53
  it("should handle nat type", () => {
54
54
  const field = visitor.visitNat(IDL.Nat, "amount")
55
55
 
56
- expect(field.type).toBe("number")
56
+ expect(field.type).toBe("text")
57
57
  expect(field.label).toBe("amount")
58
58
  expect(field.candidType).toBe("nat")
59
59
  expect(field.defaultValue).toBe("")
60
+ // TextField doesn't have isFloat or unsigned properties in the types
60
61
  })
61
62
 
62
63
  it("should handle int type", () => {
63
64
  const field = visitor.visitInt(IDL.Int, "balance")
64
65
 
65
- expect(field.type).toBe("number")
66
+ expect(field.type).toBe("text")
66
67
  expect(field.candidType).toBe("int")
67
68
  })
68
69
 
69
- it("should handle nat8 type", () => {
70
+ it("should handle nat8 type with min/max", () => {
70
71
  const field = visitor.visitFixedNat(IDL.Nat8 as IDL.FixedNatClass, "byte")
71
72
 
72
- expect(field.type).toBe("number")
73
- expect(field.candidType).toBe("nat8")
73
+ if (field.type === "number") {
74
+ expect(field.type).toBe("number")
75
+ expect(field.candidType).toBe("nat8")
76
+ expect(field.bits).toBe(8)
77
+ expect(field.min).toBe("0")
78
+ expect(field.max).toBe("255")
79
+ } else {
80
+ throw new Error("Expected number field for nat8")
81
+ }
74
82
  })
75
83
 
76
- it("should handle nat64 type", () => {
84
+ it("should handle nat64 type with min/max", () => {
77
85
  const field = visitor.visitFixedNat(
78
86
  IDL.Nat64 as IDL.FixedNatClass,
79
87
  "timestamp"
80
88
  )
81
89
 
82
- expect(field.type).toBe("number")
90
+ expect(field.type).toBe("text")
83
91
  expect(field.candidType).toBe("nat64")
92
+ // Large numbers are now text fields and don't carry bit/min/max metadata in the same way
84
93
  })
85
94
 
86
- it("should handle int32 type", () => {
95
+ it("should handle int32 type with min/max", () => {
87
96
  const field = visitor.visitFixedInt(
88
97
  IDL.Int32 as IDL.FixedIntClass,
89
98
  "count"
90
99
  )
91
100
 
92
- expect(field.type).toBe("number")
93
- expect(field.candidType).toBe("int32")
101
+ if (field.type === "number") {
102
+ expect(field.type).toBe("number")
103
+ expect(field.candidType).toBe("int32")
104
+ expect(field.bits).toBe(32)
105
+ expect(field.min).toBe("-2147483648")
106
+ expect(field.max).toBe("2147483647")
107
+ } else {
108
+ throw new Error("Expected number field for int32")
109
+ }
94
110
  })
95
111
 
96
112
  it("should handle float64 type", () => {
97
113
  const field = visitor.visitFloat(IDL.Float64 as IDL.FloatClass, "price")
98
114
 
99
115
  expect(field.type).toBe("number")
100
- expect(field.candidType).toBe("float")
116
+ expect(field.candidType).toBe("float64")
117
+ if (field.type === "number") {
118
+ expect(field.isFloat).toBe(true)
119
+ }
101
120
  })
102
121
  })
103
122
 
@@ -123,6 +142,8 @@ describe("ArgumentFieldVisitor", () => {
123
142
  expect(field.type).toBe("record")
124
143
  expect(field.label).toBe("person")
125
144
  expect(field.fields).toHaveLength(2)
145
+ expect(field.fieldMap.has("name")).toBe(true)
146
+ expect(field.fieldMap.has("age")).toBe(true)
126
147
 
127
148
  const nameField = field.fields.find((f) => f.label === "name")
128
149
  if (!nameField || nameField.type !== "text") {
@@ -132,13 +153,13 @@ describe("ArgumentFieldVisitor", () => {
132
153
  expect(nameField.defaultValue).toBe("")
133
154
 
134
155
  const ageField = field.fields.find((f) => f.label === "age")
135
- if (!ageField || ageField.type !== "number") {
136
- throw new Error("Age field not found or not number")
156
+ if (!ageField || ageField.type !== "text") {
157
+ throw new Error("Age field not found or not text")
137
158
  }
138
- expect(ageField.type).toBe("number")
159
+ expect(ageField.type).toBe("text")
139
160
  expect(ageField.candidType).toBe("nat")
140
161
 
141
- expect(field.defaultValues).toEqual({
162
+ expect(field.defaultValue).toEqual({
142
163
  name: "",
143
164
  age: "",
144
165
  })
@@ -172,7 +193,7 @@ describe("ArgumentFieldVisitor", () => {
172
193
  expect(addressField.type).toBe("record")
173
194
  expect(addressField.fields).toHaveLength(2)
174
195
 
175
- expect(field.defaultValues).toEqual({
196
+ expect(field.defaultValue).toEqual({
176
197
  name: "",
177
198
  address: {
178
199
  street: "",
@@ -223,10 +244,10 @@ describe("ArgumentFieldVisitor", () => {
223
244
 
224
245
  // Check 'amount' field
225
246
  const amountField = field.fields.find((f) => f.label === "amount")
226
- if (!amountField || amountField.type !== "number") {
227
- throw new Error("Amount field not found or not number")
247
+ if (!amountField || amountField.type !== "text") {
248
+ throw new Error("Amount field not found or not text")
228
249
  }
229
- expect(amountField.type).toBe("number")
250
+ expect(amountField.type).toBe("text")
230
251
  expect(amountField.candidType).toBe("nat")
231
252
 
232
253
  // Check optional 'fee' field
@@ -235,7 +256,7 @@ describe("ArgumentFieldVisitor", () => {
235
256
  throw new Error("Fee field not found or not optional")
236
257
  }
237
258
  expect(feeField.type).toBe("optional")
238
- expect(feeField.innerField.type).toBe("number")
259
+ expect(feeField.innerField.type).toBe("text")
239
260
  })
240
261
  })
241
262
 
@@ -261,10 +282,15 @@ describe("ArgumentFieldVisitor", () => {
261
282
  expect(field.options).toEqual(["Inactive", "Active", "Pending"])
262
283
  expect(field.defaultOption).toBe("Inactive")
263
284
  expect(field.fields).toHaveLength(3)
285
+ expect(field.optionMap.has("Active")).toBe(true)
264
286
 
265
287
  field.fields.forEach((f) => {
266
288
  expect(f.type).toBe("null")
267
289
  })
290
+
291
+ // Test getOptionDefault helper
292
+ expect(field.getOptionDefault("Active")).toEqual({ Active: null })
293
+ expect(field.getOptionDefault("Pending")).toEqual({ Pending: null })
268
294
  })
269
295
 
270
296
  it("should handle variant with payloads", () => {
@@ -312,10 +338,10 @@ describe("ArgumentFieldVisitor", () => {
312
338
  expect(transferField.fields).toHaveLength(2)
313
339
 
314
340
  const burnField = field.fields.find((f) => f.label === "Burn")
315
- if (!burnField || burnField.type !== "number") {
316
- throw new Error("Burn field not found or not number")
341
+ if (!burnField || burnField.type !== "text") {
342
+ throw new Error("Burn field not found or not text")
317
343
  }
318
- expect(burnField.type).toBe("number")
344
+ expect(burnField.type).toBe("text")
319
345
  })
320
346
 
321
347
  it("should handle Result variant (Ok/Err)", () => {
@@ -337,10 +363,10 @@ describe("ArgumentFieldVisitor", () => {
337
363
  expect(field.options).toContain("Err")
338
364
 
339
365
  const okField = field.fields.find((f) => f.label === "Ok")
340
- if (!okField || okField.type !== "number") {
341
- throw new Error("Ok field not found or not number")
366
+ if (!okField || okField.type !== "text") {
367
+ throw new Error("Ok field not found or not text")
342
368
  }
343
- expect(okField.type).toBe("number")
369
+ expect(okField.type).toBe("text")
344
370
 
345
371
  const errField = field.fields.find((f) => f.label === "Err")
346
372
  if (!errField || errField.type !== "text") {
@@ -359,8 +385,8 @@ describe("ArgumentFieldVisitor", () => {
359
385
  expect(field.label).toBe("pair")
360
386
  expect(field.fields).toHaveLength(2)
361
387
  expect(field.fields[0].type).toBe("text")
362
- expect(field.fields[1].type).toBe("number")
363
- expect(field.defaultValues).toEqual(["", ""])
388
+ expect(field.fields[1].type).toBe("text")
389
+ expect(field.defaultValue).toEqual(["", ""])
364
390
  })
365
391
 
366
392
  it("triple tuple", () => {
@@ -374,9 +400,9 @@ describe("ArgumentFieldVisitor", () => {
374
400
  expect(field.type).toBe("tuple")
375
401
  expect(field.fields).toHaveLength(3)
376
402
  expect(field.fields[0].type).toBe("principal")
377
- expect(field.fields[1].type).toBe("number")
403
+ expect(field.fields[1].type).toBe("text")
378
404
  expect(field.fields[2].type).toBe("boolean")
379
- expect(field.defaultValues).toEqual(["", "", false])
405
+ expect(field.defaultValue).toEqual(["", "", false])
380
406
  })
381
407
  })
382
408
 
@@ -389,6 +415,7 @@ describe("ArgumentFieldVisitor", () => {
389
415
  expect(field.label).toBe("nickname")
390
416
  expect(field.defaultValue).toBe(null)
391
417
  expect(field.innerField.type).toBe("text")
418
+ expect(field.getInnerDefault()).toBe("")
392
419
  })
393
420
 
394
421
  it("should handle optional record", () => {
@@ -408,6 +435,9 @@ describe("ArgumentFieldVisitor", () => {
408
435
  } else {
409
436
  throw new Error("Inner field is not record")
410
437
  }
438
+
439
+ // Test getInnerDefault helper
440
+ expect(field.getInnerDefault()).toEqual({ name: "", value: "" })
411
441
  })
412
442
 
413
443
  it("should handle nested optional", () => {
@@ -420,7 +450,7 @@ describe("ArgumentFieldVisitor", () => {
420
450
  expect(field.innerField.type).toBe("optional")
421
451
  const inner = field.innerField
422
452
  if (inner.type === "optional") {
423
- expect(inner.innerField.type).toBe("number")
453
+ expect(inner.innerField.type).toBe("text")
424
454
  } else {
425
455
  throw new Error("Inner field is not optional")
426
456
  }
@@ -430,12 +460,13 @@ describe("ArgumentFieldVisitor", () => {
430
460
  describe("Vector Types", () => {
431
461
  it("should handle vector of primitives", () => {
432
462
  const vecType = IDL.Vec(IDL.Text)
433
- const field = visitor.visitVec(vecType, IDL.Text, "tags")
463
+ const field = visitor.visitVec(vecType, IDL.Text, "tags") as VectorField
434
464
 
435
465
  expect(field.type).toBe("vector")
436
466
  expect(field.label).toBe("tags")
437
467
  expect(field.defaultValue).toEqual([])
438
468
  expect(field.itemField.type).toBe("text")
469
+ expect(field.getItemDefault()).toBe("")
439
470
  })
440
471
 
441
472
  it("should handle vector of records", () => {
@@ -444,7 +475,7 @@ describe("ArgumentFieldVisitor", () => {
444
475
  name: IDL.Text,
445
476
  })
446
477
  const vecType = IDL.Vec(recType)
447
- const field = visitor.visitVec(vecType, recType, "items")
478
+ const field = visitor.visitVec(vecType, recType, "items") as VectorField
448
479
 
449
480
  expect(field.type).toBe("vector")
450
481
  expect(field.itemField.type).toBe("record")
@@ -454,6 +485,9 @@ describe("ArgumentFieldVisitor", () => {
454
485
  } else {
455
486
  throw new Error("Item field is not record")
456
487
  }
488
+
489
+ // Test getItemDefault helper
490
+ expect(field.getItemDefault()).toEqual({ id: "", name: "" })
457
491
  })
458
492
 
459
493
  it("blob (vec nat8)", () => {
@@ -463,6 +497,9 @@ describe("ArgumentFieldVisitor", () => {
463
497
  expect(field.type).toBe("blob")
464
498
  expect(field.label).toBe("data")
465
499
  expect(field.defaultValue).toBe("")
500
+ if (field.type === "blob") {
501
+ expect(field.acceptedFormats).toEqual(["hex", "base64", "file"])
502
+ }
466
503
  })
467
504
 
468
505
  it("should handle nested vectors", () => {
@@ -474,7 +511,7 @@ describe("ArgumentFieldVisitor", () => {
474
511
  expect(field.itemField.type).toBe("vector")
475
512
  const item = field.itemField
476
513
  if (item.type === "vector") {
477
- expect(item.itemField.type).toBe("number")
514
+ expect(item.itemField.type).toBe("text")
478
515
  } else {
479
516
  throw new Error("Item field is not vector")
480
517
  }
@@ -508,7 +545,9 @@ describe("ArgumentFieldVisitor", () => {
508
545
 
509
546
  expect(field.type).toBe("recursive")
510
547
  expect(field.label).toBe("tree")
548
+ expect(field.typeName).toBeDefined()
511
549
  expect(typeof field.extract).toBe("function")
550
+ expect(typeof field.getInnerDefault).toBe("function")
512
551
 
513
552
  // Extract should return a variant
514
553
  const extracted = field.extract()
@@ -576,6 +615,8 @@ describe("ArgumentFieldVisitor", () => {
576
615
  expect(meta.fields).toHaveLength(1)
577
616
  expect(meta.fields[0].type).toBe("text")
578
617
  expect(meta.defaultValues).toEqual([""])
618
+ expect(meta.argCount).toBe(1)
619
+ expect(meta.isNoArgs).toBe(false)
579
620
  })
580
621
 
581
622
  it("should handle update function", () => {
@@ -618,8 +659,9 @@ describe("ArgumentFieldVisitor", () => {
618
659
 
619
660
  expect(meta.fields).toHaveLength(3)
620
661
  expect(meta.fields[0].type).toBe("principal")
621
- expect(meta.fields[1].type).toBe("number")
662
+ expect(meta.fields[1].type).toBe("text")
622
663
  expect(meta.fields[2].type).toBe("optional")
664
+ expect(meta.argCount).toBe(3)
623
665
  })
624
666
 
625
667
  it("should handle function with no arguments", () => {
@@ -629,6 +671,8 @@ describe("ArgumentFieldVisitor", () => {
629
671
  expect(meta.functionType).toBe("query")
630
672
  expect(meta.fields).toHaveLength(0)
631
673
  expect(meta.defaultValues).toEqual([])
674
+ expect(meta.argCount).toBe(0)
675
+ expect(meta.isNoArgs).toBe(true)
632
676
  })
633
677
  })
634
678
 
@@ -685,15 +729,16 @@ describe("ArgumentFieldVisitor", () => {
685
729
  const getMetadataMeta = serviceMeta["get_metadata"]
686
730
  expect(getMetadataMeta.functionType).toBe("query")
687
731
  expect(getMetadataMeta.fields).toHaveLength(0)
732
+ expect(getMetadataMeta.isNoArgs).toBe(true)
688
733
  })
689
734
  })
690
735
 
691
736
  // ════════════════════════════════════════════════════════════════════════
692
- // Path Generation
737
+ // Name (Path) Generation
693
738
  // ════════════════════════════════════════════════════════════════════════
694
739
 
695
- describe("Path Generation", () => {
696
- it("should generate correct paths for nested records", () => {
740
+ describe("Name Generation", () => {
741
+ it("should generate correct names for nested records", () => {
697
742
  const funcType = IDL.Func(
698
743
  [
699
744
  IDL.Record({
@@ -713,28 +758,28 @@ describe("ArgumentFieldVisitor", () => {
713
758
  if (argRecord.type !== "record") {
714
759
  throw new Error("Expected record field")
715
760
  }
716
- expect(argRecord.path).toBe("[0]")
761
+ expect(argRecord.name).toBe("[0]")
717
762
 
718
763
  const userRecord = argRecord.fields.find((f) => f.label === "user")
719
764
  if (!userRecord || userRecord.type !== "record") {
720
765
  throw new Error("User record not found or not record")
721
766
  }
722
- expect(userRecord.path).toBe("[0].user")
767
+ expect(userRecord.name).toBe("[0].user")
723
768
 
724
769
  const nameField = userRecord.fields.find((f) => f.label === "name")
725
770
  if (!nameField || nameField.type !== "text") {
726
771
  throw new Error("Name field not found or not text")
727
772
  }
728
- expect(nameField.path).toBe("[0].user.name")
773
+ expect(nameField.name).toBe("[0].user.name")
729
774
 
730
775
  const ageField = userRecord.fields.find((f) => f.label === "age")
731
- if (!ageField || ageField.type !== "number") {
732
- throw new Error("Age field not found or not number")
776
+ if (!ageField || ageField.type !== "text") {
777
+ throw new Error("Age field not found or not text")
733
778
  }
734
- expect(ageField.path).toBe("[0].user.age")
779
+ expect(ageField.name).toBe("[0].user.age")
735
780
  })
736
781
 
737
- it("should generate correct paths for vectors", () => {
782
+ it("should generate correct names for vectors", () => {
738
783
  const funcType = IDL.Func([IDL.Vec(IDL.Text)], [], [])
739
784
  const meta = visitor.visitFunc(funcType, "addTags")
740
785
 
@@ -742,8 +787,8 @@ describe("ArgumentFieldVisitor", () => {
742
787
  if (vecField.type !== "vector") {
743
788
  throw new Error("Expected vector field")
744
789
  }
745
- expect(vecField.path).toBe("[0]")
746
- expect(vecField.itemField.path).toBe("[0][0]")
790
+ expect(vecField.name).toBe("[0]")
791
+ expect(vecField.itemField.name).toBe("[0][0]")
747
792
  })
748
793
  })
749
794
 
@@ -801,10 +846,10 @@ describe("ArgumentFieldVisitor", () => {
801
846
 
802
847
  // Check amount field
803
848
  const amountField = field.fields.find((f) => f.label === "amount")
804
- if (!amountField || amountField.type !== "number") {
805
- throw new Error("Amount field not found or not number")
849
+ if (!amountField || amountField.type !== "text") {
850
+ throw new Error("Amount field not found or not text")
806
851
  }
807
- expect(amountField.type).toBe("number")
852
+ expect(amountField.type).toBe("text")
808
853
  expect(amountField.candidType).toBe("nat")
809
854
  })
810
855
 
@@ -879,4 +924,52 @@ describe("ArgumentFieldVisitor", () => {
879
924
  expect(transferField.fields.length).toBeGreaterThan(3)
880
925
  })
881
926
  })
927
+
928
+ // ════════════════════════════════════════════════════════════════════════
929
+ // Helper Methods
930
+ // ════════════════════════════════════════════════════════════════════════
931
+
932
+ describe("Helper Methods", () => {
933
+ it("variant getOptionDefault should return correct defaults", () => {
934
+ const statusType = IDL.Variant({
935
+ Active: IDL.Null,
936
+ Pending: IDL.Record({ reason: IDL.Text }),
937
+ })
938
+ const field = visitor.visitVariant(
939
+ statusType,
940
+ [
941
+ ["Active", IDL.Null],
942
+ ["Pending", IDL.Record({ reason: IDL.Text })],
943
+ ],
944
+ "status"
945
+ )
946
+
947
+ expect(field.getOptionDefault("Active")).toEqual({ Active: null })
948
+ expect(field.getOptionDefault("Pending")).toEqual({
949
+ Pending: { reason: "" },
950
+ })
951
+ })
952
+
953
+ it("vector getItemDefault should return item default", () => {
954
+ const vecType = IDL.Vec(IDL.Record({ name: IDL.Text }))
955
+ const field = visitor.visitVec(
956
+ vecType,
957
+ IDL.Record({ name: IDL.Text }),
958
+ "items"
959
+ ) as VectorField
960
+
961
+ expect(field.getItemDefault()).toEqual({ name: "" })
962
+ })
963
+
964
+ it("optional getInnerDefault should return inner default", () => {
965
+ const optType = IDL.Opt(IDL.Record({ value: IDL.Nat }))
966
+ const field = visitor.visitOpt(
967
+ optType,
968
+ IDL.Record({ value: IDL.Nat }),
969
+ "config"
970
+ )
971
+
972
+ expect(field.getInnerDefault()).toEqual({ value: "" })
973
+ })
974
+ })
882
975
  })