@ic-reactor/candid 3.0.2-beta.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +33 -1
  2. package/dist/adapter.js +2 -1
  3. package/dist/adapter.js.map +1 -1
  4. package/dist/display-reactor.d.ts +4 -13
  5. package/dist/display-reactor.d.ts.map +1 -1
  6. package/dist/display-reactor.js +22 -8
  7. package/dist/display-reactor.js.map +1 -1
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/metadata-display-reactor.d.ts +108 -0
  13. package/dist/metadata-display-reactor.d.ts.map +1 -0
  14. package/dist/metadata-display-reactor.js +141 -0
  15. package/dist/metadata-display-reactor.js.map +1 -0
  16. package/dist/reactor.d.ts +1 -1
  17. package/dist/reactor.d.ts.map +1 -1
  18. package/dist/reactor.js +10 -6
  19. package/dist/reactor.js.map +1 -1
  20. package/dist/types.d.ts +38 -7
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/utils.d.ts +4 -4
  23. package/dist/utils.d.ts.map +1 -1
  24. package/dist/utils.js +33 -10
  25. package/dist/utils.js.map +1 -1
  26. package/dist/visitor/arguments/helpers.d.ts +55 -0
  27. package/dist/visitor/arguments/helpers.d.ts.map +1 -0
  28. package/dist/visitor/arguments/helpers.js +123 -0
  29. package/dist/visitor/arguments/helpers.js.map +1 -0
  30. package/dist/visitor/arguments/index.d.ts +101 -0
  31. package/dist/visitor/arguments/index.d.ts.map +1 -0
  32. package/dist/visitor/arguments/index.js +780 -0
  33. package/dist/visitor/arguments/index.js.map +1 -0
  34. package/dist/visitor/arguments/types.d.ts +270 -0
  35. package/dist/visitor/arguments/types.d.ts.map +1 -0
  36. package/dist/visitor/arguments/types.js +26 -0
  37. package/dist/visitor/arguments/types.js.map +1 -0
  38. package/dist/visitor/constants.d.ts +4 -0
  39. package/dist/visitor/constants.d.ts.map +1 -0
  40. package/dist/visitor/constants.js +73 -0
  41. package/dist/visitor/constants.js.map +1 -0
  42. package/dist/visitor/helpers.d.ts +30 -0
  43. package/dist/visitor/helpers.d.ts.map +1 -0
  44. package/dist/visitor/helpers.js +204 -0
  45. package/dist/visitor/helpers.js.map +1 -0
  46. package/dist/visitor/index.d.ts +5 -0
  47. package/dist/visitor/index.d.ts.map +1 -0
  48. package/dist/visitor/index.js +5 -0
  49. package/dist/visitor/index.js.map +1 -0
  50. package/dist/visitor/returns/index.d.ts +38 -0
  51. package/dist/visitor/returns/index.d.ts.map +1 -0
  52. package/dist/visitor/returns/index.js +460 -0
  53. package/dist/visitor/returns/index.js.map +1 -0
  54. package/dist/visitor/returns/types.d.ts +202 -0
  55. package/dist/visitor/returns/types.d.ts.map +1 -0
  56. package/dist/visitor/returns/types.js +2 -0
  57. package/dist/visitor/returns/types.js.map +1 -0
  58. package/dist/visitor/types.d.ts +19 -0
  59. package/dist/visitor/types.d.ts.map +1 -0
  60. package/dist/visitor/types.js +2 -0
  61. package/dist/visitor/types.js.map +1 -0
  62. package/package.json +16 -7
  63. package/src/adapter.ts +446 -0
  64. package/src/constants.ts +11 -0
  65. package/src/display-reactor.ts +337 -0
  66. package/src/index.ts +8 -0
  67. package/src/metadata-display-reactor.ts +230 -0
  68. package/src/reactor.ts +199 -0
  69. package/src/types.ts +127 -0
  70. package/src/utils.ts +60 -0
  71. package/src/visitor/arguments/helpers.ts +153 -0
  72. package/src/visitor/arguments/index.test.ts +1439 -0
  73. package/src/visitor/arguments/index.ts +981 -0
  74. package/src/visitor/arguments/schema.test.ts +324 -0
  75. package/src/visitor/arguments/types.ts +387 -0
  76. package/src/visitor/constants.ts +76 -0
  77. package/src/visitor/helpers.test.ts +274 -0
  78. package/src/visitor/helpers.ts +223 -0
  79. package/src/visitor/index.ts +4 -0
  80. package/src/visitor/returns/index.test.ts +2377 -0
  81. package/src/visitor/returns/index.ts +658 -0
  82. package/src/visitor/returns/types.ts +302 -0
  83. package/src/visitor/types.ts +75 -0
@@ -0,0 +1,324 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { IDL } from "@icp-sdk/core/candid"
3
+ import { Principal } from "@icp-sdk/core/principal"
4
+ import { FieldVisitor } from "./index"
5
+ import * as z from "zod"
6
+
7
+ describe("FieldVisitor Schema Generation", () => {
8
+ const visitor = new FieldVisitor()
9
+
10
+ // ════════════════════════════════════════════════════════════════════════
11
+ // Primitive Types
12
+ // ════════════════════════════════════════════════════════════════════════
13
+
14
+ describe("Primitive Types", () => {
15
+ it("should generate string schema for text", () => {
16
+ const field = visitor.visitText(IDL.Text, "username")
17
+ const schema = field.schema
18
+
19
+ expect(schema).toBeDefined()
20
+ expect(schema.parse("hello")).toBe("hello")
21
+ expect(() => schema.parse(123)).toThrow()
22
+ })
23
+
24
+ it("should generate boolean schema for bool", () => {
25
+ const field = visitor.visitBool(IDL.Bool, "isActive")
26
+ const schema = field.schema
27
+
28
+ expect(schema.parse(true)).toBe(true)
29
+ expect(schema.parse(false)).toBe(false)
30
+ expect(() => schema.parse("true")).toThrow()
31
+ })
32
+
33
+ it("should generate null schema for null", () => {
34
+ const field = visitor.visitNull(IDL.Null, "void")
35
+ const schema = field.schema
36
+
37
+ expect(schema.parse(null)).toBe(null)
38
+ expect(() => schema.parse(undefined)).toThrow()
39
+ })
40
+
41
+ it("should generate string schema for numbers (form input matching)", () => {
42
+ // The visitor currently generates string schemas for numbers to match form inputs
43
+ const natField = visitor.visitNat(IDL.Nat, "amount")
44
+ expect(natField.schema.parse("100")).toBe("100")
45
+
46
+ const intField = visitor.visitInt(IDL.Int, "balance")
47
+ expect(intField.schema.parse("-50")).toBe("-50")
48
+ })
49
+
50
+ it("should generate principal schema", () => {
51
+ const field = visitor.visitPrincipal(IDL.Principal, "owner")
52
+ const schema = field.schema
53
+
54
+ const p = Principal.fromText("2vxsx-fae")
55
+ // Should accept Principal instance
56
+ expect(schema.parse(p)).toEqual(p)
57
+ // Should accept valid string representation
58
+ expect(schema.parse("2vxsx-fae")).toBe("2vxsx-fae")
59
+
60
+ // Should reject invalid
61
+ expect(() => schema.parse("invalid-principal")).toThrow()
62
+ })
63
+ })
64
+
65
+ // ════════════════════════════════════════════════════════════════════════
66
+ // Format Detection & Validation
67
+ // ════════════════════════════════════════════════════════════════════════
68
+
69
+ describe("Format Detection & Validation", () => {
70
+ describe("Text Formats", () => {
71
+ it("should detect email format", () => {
72
+ const field = visitor.visitText(IDL.Text, "user_email")
73
+
74
+ expect(field.format).toBe("email")
75
+ expect(field.inputProps).toMatchObject({
76
+ type: "email",
77
+ inputMode: "email",
78
+ })
79
+
80
+ // Valid email
81
+ expect(field.schema.parse("test@example.com")).toBe("test@example.com")
82
+ // Invalid email
83
+ expect(() => field.schema.parse("invalid-email")).toThrow()
84
+ })
85
+
86
+ it("should detect url format", () => {
87
+ const field = visitor.visitText(IDL.Text, "website_link")
88
+
89
+ expect(field.format).toBe("url")
90
+ expect(field.inputProps).toMatchObject({
91
+ type: "url",
92
+ inputMode: "url",
93
+ })
94
+
95
+ // Valid URL
96
+ expect(field.schema.parse("https://example.com")).toBe(
97
+ "https://example.com"
98
+ )
99
+ // Invalid URL
100
+ expect(() => field.schema.parse("not-a-url")).toThrow()
101
+ })
102
+
103
+ it("should detect uuid format", () => {
104
+ const field = visitor.visitText(IDL.Text, "transaction_uuid")
105
+
106
+ expect(field.format).toBe("uuid")
107
+
108
+ const validUuid = "123e4567-e89b-12d3-a456-426614174000"
109
+ expect(field.schema.parse(validUuid)).toBe(validUuid)
110
+
111
+ expect(() => field.schema.parse("invalid-uuid")).toThrow()
112
+ })
113
+
114
+ it("should detect ethereum address format", () => {
115
+ const field = visitor.visitText(IDL.Text, "eth_address")
116
+
117
+ expect(field.format).toBe("eth")
118
+ expect(field.inputProps.pattern).toContain("0x")
119
+ })
120
+
121
+ it("should fallback to plain text for unknown formats", () => {
122
+ const field = visitor.visitText(IDL.Text, "some_random_field")
123
+
124
+ expect(field.format).toBe("plain")
125
+ expect(field.inputProps).toMatchObject({
126
+ type: "text",
127
+ })
128
+ })
129
+ })
130
+
131
+ describe("Number Formats", () => {
132
+ it("should detect timestamp format", () => {
133
+ const field = visitor.visitInt(IDL.Int, "created_at")
134
+
135
+ expect(field.format).toBe("timestamp")
136
+ })
137
+
138
+ it("should detect cycle format", () => {
139
+ const field = visitor.visitNat(IDL.Nat, "cycles_balance")
140
+
141
+ expect(field.format).toBe("cycle")
142
+ })
143
+
144
+ it("should fallback to normal format", () => {
145
+ const field = visitor.visitNat(IDL.Nat, "quantity")
146
+
147
+ expect(field.format).toBe("plain")
148
+ })
149
+ })
150
+
151
+ describe("Principal Format", () => {
152
+ it("should detect principal format and set properties", () => {
153
+ const field = visitor.visitPrincipal(
154
+ IDL.Principal,
155
+ "controller_principal"
156
+ )
157
+
158
+ expect(field.format).toBe("principal")
159
+ expect(field.inputProps).toMatchObject({
160
+ minLength: 7,
161
+ maxLength: 64,
162
+ })
163
+ })
164
+ })
165
+ })
166
+
167
+ // ════════════════════════════════════════════════════════════════════════
168
+ // Compound Types
169
+ // ════════════════════════════════════════════════════════════════════════
170
+
171
+ describe("Record Types", () => {
172
+ it("should generate object schema for record", () => {
173
+ const recordType = IDL.Record({
174
+ name: IDL.Text,
175
+ age: IDL.Nat,
176
+ })
177
+ const field = visitor.visitRecord(
178
+ recordType,
179
+ [
180
+ ["name", IDL.Text],
181
+ ["age", IDL.Nat],
182
+ ],
183
+ "person"
184
+ )
185
+
186
+ const validData = { name: "John", age: "30" }
187
+ expect(field.schema.parse(validData)).toEqual(validData)
188
+
189
+ expect(() => field.schema.parse({ name: "John" })).toThrow() // missing age
190
+ expect(() => field.schema.parse({ name: 123, age: "30" })).toThrow() // invalid type
191
+ })
192
+ })
193
+
194
+ describe("Variant Types", () => {
195
+ it("should generate union schema for variant", () => {
196
+ const variantType = IDL.Variant({
197
+ Ok: IDL.Text,
198
+ Err: IDL.Text,
199
+ })
200
+ const field = visitor.visitVariant(
201
+ variantType,
202
+ [
203
+ ["Ok", IDL.Text],
204
+ ["Err", IDL.Text],
205
+ ],
206
+ "result"
207
+ )
208
+ const schema = field.schema as z.ZodUnion<any>
209
+
210
+ expect(schema.parse({ _type: "Ok", Ok: "Success" })).toEqual({
211
+ _type: "Ok",
212
+ Ok: "Success",
213
+ })
214
+ expect(schema.parse({ _type: "Err", Err: "Error" })).toEqual({
215
+ _type: "Err",
216
+ Err: "Error",
217
+ })
218
+
219
+ expect(() => schema.parse({ _type: "Other", Other: "value" })).toThrow()
220
+ })
221
+ })
222
+
223
+ describe("Tuple Types", () => {
224
+ it("should generate tuple schema", () => {
225
+ const tupleType = IDL.Tuple(IDL.Text, IDL.Nat)
226
+ const field = visitor.visitTuple(tupleType, [IDL.Text, IDL.Nat], "pair")
227
+ const schema = field.schema as z.ZodTuple<any>
228
+
229
+ expect(schema.parse(["key", "100"])).toEqual(["key", "100"])
230
+ expect(() => schema.parse(["key"])).toThrow()
231
+ })
232
+ })
233
+
234
+ describe("Optional Types", () => {
235
+ it("should generate nullable/optional schema", () => {
236
+ const optType = IDL.Opt(IDL.Text)
237
+ const field = visitor.visitOpt(optType, IDL.Text, "maybe")
238
+ const schema = field.schema
239
+
240
+ expect(schema.parse("value")).toBe("value")
241
+ expect(schema.parse(null)).toBe(null)
242
+ expect(schema.parse(undefined)).toBe(null) // nullish() allows undefined -> null
243
+ })
244
+ })
245
+
246
+ describe("Vector Types", () => {
247
+ it("should generate array schema", () => {
248
+ const vecType = IDL.Vec(IDL.Text)
249
+ const field = visitor.visitVec(vecType, IDL.Text, "tags")
250
+ const schema = field.schema
251
+
252
+ expect(schema.parse(["a", "b"])).toEqual(["a", "b"])
253
+ expect(schema.parse([])).toEqual([])
254
+ expect(() => schema.parse("not array")).toThrow()
255
+ })
256
+
257
+ it("should generate special schema for blob", () => {
258
+ const blobType = IDL.Vec(IDL.Nat8)
259
+ const field = visitor.visitVec(blobType, IDL.Nat8, "data")
260
+ const schema = field.schema
261
+
262
+ // Blob accepts string (hex) or array of numbers
263
+ expect(schema.parse("deadbeef")).toBe("deadbeef")
264
+ expect(schema.parse([1, 2, 3])).toEqual([1, 2, 3])
265
+ expect(() => schema.parse(123)).toThrow()
266
+ })
267
+ })
268
+
269
+ // ════════════════════════════════════════════════════════════════════════
270
+ // Recursive Types
271
+ // ════════════════════════════════════════════════════════════════════════
272
+
273
+ describe("Recursive Types", () => {
274
+ it("should handle recursive schemas", () => {
275
+ const List = IDL.Rec()
276
+ const ListVariant = IDL.Variant({
277
+ Nil: IDL.Null,
278
+ Cons: IDL.Record({
279
+ head: IDL.Nat,
280
+ tail: List,
281
+ }),
282
+ })
283
+ List.fill(ListVariant)
284
+
285
+ const field = visitor.visitRec(List, ListVariant, "list")
286
+ const schema = field.schema
287
+
288
+ const validList = {
289
+ _type: "Cons",
290
+ Cons: {
291
+ head: "1",
292
+ tail: {
293
+ _type: "Cons",
294
+ Cons: {
295
+ head: "2",
296
+ tail: { _type: "Nil" },
297
+ },
298
+ },
299
+ },
300
+ }
301
+
302
+ expect(schema.parse(validList)).toEqual(validList)
303
+ })
304
+ })
305
+
306
+ // ════════════════════════════════════════════════════════════════════════
307
+ // Method Schema
308
+ // ════════════════════════════════════════════════════════════════════════
309
+
310
+ describe("Method Schema", () => {
311
+ it("should generate tuple schema for function arguments", () => {
312
+ const funcType = IDL.Func([IDL.Text, IDL.Nat], [], [])
313
+ const meta = visitor.visitFunc(funcType, "myMethod")
314
+
315
+ const schema = meta.schema
316
+ expect(schema).toBeDefined()
317
+
318
+ const validArgs = ["hello", "123"]
319
+ expect(schema.parse(validArgs)).toEqual(validArgs)
320
+
321
+ expect(() => schema.parse(["hello"])).toThrow()
322
+ })
323
+ })
324
+ })