@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.
- package/README.md +33 -1
- package/dist/adapter.js +2 -1
- package/dist/adapter.js.map +1 -1
- package/dist/display-reactor.d.ts +4 -13
- package/dist/display-reactor.d.ts.map +1 -1
- package/dist/display-reactor.js +22 -8
- package/dist/display-reactor.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata-display-reactor.d.ts +108 -0
- package/dist/metadata-display-reactor.d.ts.map +1 -0
- package/dist/metadata-display-reactor.js +141 -0
- package/dist/metadata-display-reactor.js.map +1 -0
- package/dist/reactor.d.ts +1 -1
- package/dist/reactor.d.ts.map +1 -1
- package/dist/reactor.js +10 -6
- package/dist/reactor.js.map +1 -1
- package/dist/types.d.ts +38 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +4 -4
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +33 -10
- package/dist/utils.js.map +1 -1
- package/dist/visitor/arguments/helpers.d.ts +55 -0
- package/dist/visitor/arguments/helpers.d.ts.map +1 -0
- package/dist/visitor/arguments/helpers.js +123 -0
- package/dist/visitor/arguments/helpers.js.map +1 -0
- package/dist/visitor/arguments/index.d.ts +101 -0
- package/dist/visitor/arguments/index.d.ts.map +1 -0
- package/dist/visitor/arguments/index.js +780 -0
- package/dist/visitor/arguments/index.js.map +1 -0
- package/dist/visitor/arguments/types.d.ts +270 -0
- package/dist/visitor/arguments/types.d.ts.map +1 -0
- package/dist/visitor/arguments/types.js +26 -0
- package/dist/visitor/arguments/types.js.map +1 -0
- package/dist/visitor/constants.d.ts +4 -0
- package/dist/visitor/constants.d.ts.map +1 -0
- package/dist/visitor/constants.js +73 -0
- package/dist/visitor/constants.js.map +1 -0
- package/dist/visitor/helpers.d.ts +30 -0
- package/dist/visitor/helpers.d.ts.map +1 -0
- package/dist/visitor/helpers.js +204 -0
- package/dist/visitor/helpers.js.map +1 -0
- package/dist/visitor/index.d.ts +5 -0
- package/dist/visitor/index.d.ts.map +1 -0
- package/dist/visitor/index.js +5 -0
- package/dist/visitor/index.js.map +1 -0
- package/dist/visitor/returns/index.d.ts +38 -0
- package/dist/visitor/returns/index.d.ts.map +1 -0
- package/dist/visitor/returns/index.js +460 -0
- package/dist/visitor/returns/index.js.map +1 -0
- package/dist/visitor/returns/types.d.ts +202 -0
- package/dist/visitor/returns/types.d.ts.map +1 -0
- package/dist/visitor/returns/types.js +2 -0
- package/dist/visitor/returns/types.js.map +1 -0
- package/dist/visitor/types.d.ts +19 -0
- package/dist/visitor/types.d.ts.map +1 -0
- package/dist/visitor/types.js +2 -0
- package/dist/visitor/types.js.map +1 -0
- package/package.json +16 -7
- package/src/adapter.ts +446 -0
- package/src/constants.ts +11 -0
- package/src/display-reactor.ts +337 -0
- package/src/index.ts +8 -0
- package/src/metadata-display-reactor.ts +230 -0
- package/src/reactor.ts +199 -0
- package/src/types.ts +127 -0
- package/src/utils.ts +60 -0
- package/src/visitor/arguments/helpers.ts +153 -0
- package/src/visitor/arguments/index.test.ts +1439 -0
- package/src/visitor/arguments/index.ts +981 -0
- package/src/visitor/arguments/schema.test.ts +324 -0
- package/src/visitor/arguments/types.ts +387 -0
- package/src/visitor/constants.ts +76 -0
- package/src/visitor/helpers.test.ts +274 -0
- package/src/visitor/helpers.ts +223 -0
- package/src/visitor/index.ts +4 -0
- package/src/visitor/returns/index.test.ts +2377 -0
- package/src/visitor/returns/index.ts +658 -0
- package/src/visitor/returns/types.ts +302 -0
- 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
|
+
})
|