@ic-reactor/candid 3.0.11-beta.2 → 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.
- package/dist/display-reactor.d.ts +1 -2
- package/dist/display-reactor.d.ts.map +1 -1
- package/dist/display-reactor.js +1 -1
- package/dist/display-reactor.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata-display-reactor.d.ts +3 -3
- package/dist/metadata-display-reactor.d.ts.map +1 -1
- package/dist/visitor/arguments/index.d.ts +58 -39
- package/dist/visitor/arguments/index.d.ts.map +1 -1
- package/dist/visitor/arguments/index.js +273 -81
- package/dist/visitor/arguments/index.js.map +1 -1
- package/dist/visitor/arguments/types.d.ts +228 -45
- package/dist/visitor/arguments/types.d.ts.map +1 -1
- package/dist/visitor/arguments/types.js +40 -1
- package/dist/visitor/arguments/types.js.map +1 -1
- package/dist/visitor/helpers.d.ts +1 -1
- package/dist/visitor/helpers.d.ts.map +1 -1
- package/dist/visitor/returns/index.d.ts +1 -2
- package/dist/visitor/returns/index.d.ts.map +1 -1
- package/dist/visitor/returns/index.js +2 -3
- package/dist/visitor/returns/index.js.map +1 -1
- package/dist/visitor/types.d.ts +2 -3
- package/dist/visitor/types.d.ts.map +1 -1
- package/dist/visitor/types.js +1 -2
- package/dist/visitor/types.js.map +1 -1
- package/package.json +6 -3
- package/src/display-reactor.ts +4 -6
- package/src/index.ts +0 -1
- package/src/metadata-display-reactor.ts +6 -6
- package/src/visitor/arguments/README.md +230 -0
- package/src/visitor/arguments/index.test.ts +144 -51
- package/src/visitor/arguments/index.ts +351 -146
- package/src/visitor/arguments/schema.test.ts +215 -0
- package/src/visitor/arguments/types.ts +287 -61
- package/src/visitor/helpers.ts +1 -1
- package/src/visitor/returns/index.test.ts +1 -1
- package/src/visitor/returns/index.ts +2 -3
- package/src/visitor/types.ts +2 -3
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { isQuery } from "../helpers"
|
|
2
|
-
import { IDL } from "../types"
|
|
3
2
|
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
Field,
|
|
4
|
+
RecordField,
|
|
5
|
+
VariantField,
|
|
6
|
+
TupleField,
|
|
7
|
+
OptionalField,
|
|
8
|
+
VectorField,
|
|
9
|
+
BlobField,
|
|
10
|
+
RecursiveField,
|
|
11
|
+
PrincipalField,
|
|
12
|
+
NumberField,
|
|
13
|
+
BooleanField,
|
|
14
|
+
NullField,
|
|
15
|
+
TextField,
|
|
16
|
+
UnknownField,
|
|
17
|
+
ArgumentsMeta,
|
|
18
|
+
ArgumentsServiceMeta,
|
|
20
19
|
} from "./types"
|
|
20
|
+
|
|
21
|
+
import { IDL } from "@icp-sdk/core/candid"
|
|
22
|
+
import { Principal } from "@icp-sdk/core/principal"
|
|
21
23
|
import { BaseActor, FunctionName } from "@ic-reactor/core"
|
|
24
|
+
import * as z from "zod"
|
|
22
25
|
|
|
23
26
|
export * from "./types"
|
|
24
27
|
|
|
@@ -31,66 +34,87 @@ export * from "./types"
|
|
|
31
34
|
* 2. **No value dependencies** - metadata is independent of actual values
|
|
32
35
|
* 3. **Form-framework agnostic** - output can be used with TanStack, React Hook Form, etc.
|
|
33
36
|
* 4. **Efficient** - single traversal, no runtime type checking
|
|
37
|
+
* 5. **TanStack Form optimized** - name paths compatible with TanStack Form patterns
|
|
34
38
|
*
|
|
35
39
|
* ## Output Structure
|
|
36
40
|
*
|
|
37
41
|
* Each field has:
|
|
38
42
|
* - `type`: The field type (record, variant, text, number, etc.)
|
|
39
43
|
* - `label`: Human-readable label from Candid
|
|
40
|
-
* - `
|
|
44
|
+
* - `name`: TanStack Form compatible path (e.g., "[0]", "[0].owner", "tags[1]")
|
|
41
45
|
* - `defaultValue`: Initial value for the form
|
|
46
|
+
* - `schema`: Zod schema for validation
|
|
42
47
|
* - Type-specific properties (options for variant, fields for record, etc.)
|
|
48
|
+
* - Helper methods for dynamic forms (getOptionDefault, getItemDefault, etc.)
|
|
49
|
+
*
|
|
50
|
+
* ## Usage with TanStack Form
|
|
43
51
|
*
|
|
44
52
|
* @example
|
|
45
53
|
* ```typescript
|
|
54
|
+
* import { useForm } from '@tanstack/react-form'
|
|
55
|
+
* import { ArgumentFieldVisitor } from '@ic-reactor/candid'
|
|
56
|
+
*
|
|
46
57
|
* const visitor = new ArgumentFieldVisitor()
|
|
47
58
|
* const serviceMeta = service.accept(visitor, null)
|
|
48
|
-
*
|
|
49
|
-
* // For a specific method
|
|
50
59
|
* const methodMeta = serviceMeta["icrc1_transfer"]
|
|
51
|
-
*
|
|
52
|
-
*
|
|
60
|
+
*
|
|
61
|
+
* const form = useForm({
|
|
62
|
+
* defaultValues: methodMeta.defaultValue,
|
|
63
|
+
* validators: { onBlur: methodMeta.schema },
|
|
64
|
+
* onSubmit: async ({ value }) => {
|
|
65
|
+
* await actor.icrc1_transfer(...value)
|
|
66
|
+
* }
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* // Render fields dynamically
|
|
70
|
+
* methodMeta.fields.map((field, index) => (
|
|
71
|
+
* <form.Field key={index} name={field.name}>
|
|
72
|
+
* {(fieldApi) => <DynamicInput field={field} fieldApi={fieldApi} />}
|
|
73
|
+
* </form.Field>
|
|
74
|
+
* ))
|
|
53
75
|
* ```
|
|
54
76
|
*/
|
|
55
77
|
export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
56
78
|
string,
|
|
57
|
-
|
|
79
|
+
Field | ArgumentsMeta<A> | ArgumentsServiceMeta<A>
|
|
58
80
|
> {
|
|
59
|
-
|
|
81
|
+
public recursiveSchemas: Map<string, z.ZodTypeAny> = new Map()
|
|
60
82
|
|
|
61
|
-
private
|
|
62
|
-
|
|
83
|
+
private nameStack: string[] = []
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute function with a name segment pushed onto the stack.
|
|
87
|
+
* Automatically manages stack cleanup.
|
|
88
|
+
*/
|
|
89
|
+
private withName<T>(name: string, fn: () => T): T {
|
|
90
|
+
this.nameStack.push(name)
|
|
63
91
|
try {
|
|
64
92
|
return fn()
|
|
65
93
|
} finally {
|
|
66
|
-
this.
|
|
94
|
+
this.nameStack.pop()
|
|
67
95
|
}
|
|
68
96
|
}
|
|
69
97
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
if (typeof key === "number") {
|
|
77
|
-
return parent ? `${parent}[${key}]` : String(key)
|
|
78
|
-
}
|
|
79
|
-
return parent ? `${parent}.${key}` : key
|
|
98
|
+
/**
|
|
99
|
+
* Get the current full name path for form binding.
|
|
100
|
+
* Returns empty string for root level.
|
|
101
|
+
*/
|
|
102
|
+
private currentName(): string {
|
|
103
|
+
return this.nameStack.join("")
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
// ════════════════════════════════════════════════════════════════════════
|
|
83
107
|
// Service & Function Level
|
|
84
108
|
// ════════════════════════════════════════════════════════════════════════
|
|
85
109
|
|
|
86
|
-
public visitService(t: IDL.ServiceClass):
|
|
87
|
-
const result = {} as
|
|
110
|
+
public visitService(t: IDL.ServiceClass): ArgumentsServiceMeta<A> {
|
|
111
|
+
const result = {} as ArgumentsServiceMeta<A>
|
|
88
112
|
|
|
89
113
|
for (const [functionName, func] of t._fields) {
|
|
90
114
|
result[functionName as FunctionName<A>] = func.accept(
|
|
91
115
|
this,
|
|
92
116
|
functionName
|
|
93
|
-
) as
|
|
117
|
+
) as ArgumentsMeta<A>
|
|
94
118
|
}
|
|
95
119
|
|
|
96
120
|
return result
|
|
@@ -99,35 +123,44 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
99
123
|
public visitFunc(
|
|
100
124
|
t: IDL.FuncClass,
|
|
101
125
|
functionName: FunctionName<A>
|
|
102
|
-
):
|
|
126
|
+
): ArgumentsMeta<A> {
|
|
103
127
|
const functionType = isQuery(t) ? "query" : "update"
|
|
128
|
+
const argCount = t.argTypes.length
|
|
104
129
|
|
|
105
130
|
const fields = t.argTypes.map((arg, index) => {
|
|
106
|
-
return this.
|
|
131
|
+
return this.withName(`[${index}]`, () =>
|
|
107
132
|
arg.accept(this, `__arg${index}`)
|
|
108
|
-
) as
|
|
133
|
+
) as Field
|
|
109
134
|
})
|
|
110
135
|
|
|
111
|
-
const defaultValues = fields.map((field) =>
|
|
136
|
+
const defaultValues = fields.map((field) => field.defaultValue)
|
|
137
|
+
|
|
138
|
+
// Handle empty args case for schema
|
|
139
|
+
// For no-arg functions, use an empty array schema
|
|
140
|
+
// For functions with args, use a proper tuple schema
|
|
141
|
+
const schema =
|
|
142
|
+
argCount === 0
|
|
143
|
+
? (z.tuple([]) as unknown as z.ZodTuple<
|
|
144
|
+
[z.ZodTypeAny, ...z.ZodTypeAny[]]
|
|
145
|
+
>)
|
|
146
|
+
: z.tuple(
|
|
147
|
+
fields.map((field) => field.schema) as [
|
|
148
|
+
z.ZodTypeAny,
|
|
149
|
+
...z.ZodTypeAny[],
|
|
150
|
+
]
|
|
151
|
+
)
|
|
112
152
|
|
|
113
153
|
return {
|
|
114
154
|
functionType,
|
|
115
155
|
functionName,
|
|
116
156
|
fields,
|
|
117
|
-
defaultValues,
|
|
157
|
+
defaultValues: defaultValues,
|
|
158
|
+
schema,
|
|
159
|
+
argCount,
|
|
160
|
+
isNoArgs: argCount === 0,
|
|
118
161
|
}
|
|
119
162
|
}
|
|
120
163
|
|
|
121
|
-
private extractDefaultValue(field: ArgumentField): unknown {
|
|
122
|
-
if ("defaultValue" in field) {
|
|
123
|
-
return field.defaultValue
|
|
124
|
-
}
|
|
125
|
-
if ("defaultValues" in field) {
|
|
126
|
-
return field.defaultValues
|
|
127
|
-
}
|
|
128
|
-
return undefined
|
|
129
|
-
}
|
|
130
|
-
|
|
131
164
|
// ════════════════════════════════════════════════════════════════════════
|
|
132
165
|
// Compound Types
|
|
133
166
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -136,26 +169,35 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
136
169
|
_t: IDL.RecordClass,
|
|
137
170
|
fields_: Array<[string, IDL.Type]>,
|
|
138
171
|
label: string
|
|
139
|
-
):
|
|
140
|
-
const
|
|
141
|
-
const fields:
|
|
142
|
-
const
|
|
172
|
+
): RecordField {
|
|
173
|
+
const name = this.currentName()
|
|
174
|
+
const fields: Field[] = []
|
|
175
|
+
const fieldMap = new Map<string, Field>()
|
|
176
|
+
const defaultValue: Record<string, unknown> = {}
|
|
177
|
+
const schemaShape: Record<string, z.ZodTypeAny> = {}
|
|
143
178
|
|
|
144
179
|
for (const [key, type] of fields_) {
|
|
145
|
-
const field = this.
|
|
180
|
+
const field = this.withName(name ? `.${key}` : key, () =>
|
|
146
181
|
type.accept(this, key)
|
|
147
|
-
) as
|
|
182
|
+
) as Field
|
|
148
183
|
|
|
149
184
|
fields.push(field)
|
|
150
|
-
|
|
185
|
+
fieldMap.set(key, field)
|
|
186
|
+
defaultValue[key] = field.defaultValue
|
|
187
|
+
schemaShape[key] = field.schema
|
|
151
188
|
}
|
|
152
189
|
|
|
190
|
+
const schema = z.object(schemaShape)
|
|
191
|
+
|
|
153
192
|
return {
|
|
154
193
|
type: "record",
|
|
155
194
|
label,
|
|
156
|
-
|
|
195
|
+
name,
|
|
157
196
|
fields,
|
|
158
|
-
|
|
197
|
+
fieldMap,
|
|
198
|
+
defaultValue,
|
|
199
|
+
schema,
|
|
200
|
+
candidType: "record",
|
|
159
201
|
}
|
|
160
202
|
}
|
|
161
203
|
|
|
@@ -163,33 +205,53 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
163
205
|
_t: IDL.VariantClass,
|
|
164
206
|
fields_: Array<[string, IDL.Type]>,
|
|
165
207
|
label: string
|
|
166
|
-
):
|
|
167
|
-
const
|
|
168
|
-
const fields:
|
|
208
|
+
): VariantField {
|
|
209
|
+
const name = this.currentName()
|
|
210
|
+
const fields: Field[] = []
|
|
169
211
|
const options: string[] = []
|
|
212
|
+
const optionMap = new Map<string, Field>()
|
|
213
|
+
const variantSchemas: z.ZodTypeAny[] = []
|
|
170
214
|
|
|
171
215
|
for (const [key, type] of fields_) {
|
|
172
|
-
const field = this.
|
|
216
|
+
const field = this.withName(`.${key}`, () =>
|
|
173
217
|
type.accept(this, key)
|
|
174
|
-
) as
|
|
218
|
+
) as Field
|
|
175
219
|
|
|
176
220
|
fields.push(field)
|
|
177
221
|
options.push(key)
|
|
222
|
+
optionMap.set(key, field)
|
|
223
|
+
variantSchemas.push(z.object({ [key]: field.schema }))
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
const defaultOption = options[0]
|
|
181
|
-
const
|
|
182
|
-
|
|
227
|
+
const firstField = fields[0]
|
|
228
|
+
const defaultValue = {
|
|
229
|
+
[defaultOption]: firstField.defaultValue,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const schema = z.union(variantSchemas as [z.ZodTypeAny, ...z.ZodTypeAny[]])
|
|
233
|
+
|
|
234
|
+
// Helper to get default value for any option
|
|
235
|
+
const getOptionDefault = (option: string): Record<string, unknown> => {
|
|
236
|
+
const optField = optionMap.get(option)
|
|
237
|
+
if (!optField) {
|
|
238
|
+
throw new Error(`Unknown variant option: ${option}`)
|
|
239
|
+
}
|
|
240
|
+
return { [option]: optField.defaultValue }
|
|
183
241
|
}
|
|
184
242
|
|
|
185
243
|
return {
|
|
186
244
|
type: "variant",
|
|
187
245
|
label,
|
|
188
|
-
|
|
246
|
+
name,
|
|
189
247
|
fields,
|
|
190
248
|
options,
|
|
191
249
|
defaultOption,
|
|
192
|
-
|
|
250
|
+
optionMap,
|
|
251
|
+
defaultValue,
|
|
252
|
+
schema,
|
|
253
|
+
getOptionDefault,
|
|
254
|
+
candidType: "variant",
|
|
193
255
|
}
|
|
194
256
|
}
|
|
195
257
|
|
|
@@ -197,27 +259,33 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
197
259
|
_t: IDL.TupleClass<T>,
|
|
198
260
|
components: IDL.Type[],
|
|
199
261
|
label: string
|
|
200
|
-
):
|
|
201
|
-
const
|
|
202
|
-
const fields:
|
|
203
|
-
const
|
|
262
|
+
): TupleField {
|
|
263
|
+
const name = this.currentName()
|
|
264
|
+
const fields: Field[] = []
|
|
265
|
+
const defaultValue: unknown[] = []
|
|
266
|
+
const schemas: z.ZodTypeAny[] = []
|
|
204
267
|
|
|
205
268
|
for (let index = 0; index < components.length; index++) {
|
|
206
269
|
const type = components[index]
|
|
207
|
-
const field = this.
|
|
270
|
+
const field = this.withName(`[${index}]`, () =>
|
|
208
271
|
type.accept(this, `_${index}_`)
|
|
209
|
-
) as
|
|
272
|
+
) as Field
|
|
210
273
|
|
|
211
274
|
fields.push(field)
|
|
212
|
-
|
|
275
|
+
defaultValue.push(field.defaultValue)
|
|
276
|
+
schemas.push(field.schema)
|
|
213
277
|
}
|
|
214
278
|
|
|
279
|
+
const schema = z.tuple(schemas as [z.ZodTypeAny, ...z.ZodTypeAny[]])
|
|
280
|
+
|
|
215
281
|
return {
|
|
216
282
|
type: "tuple",
|
|
217
283
|
label,
|
|
218
|
-
|
|
284
|
+
name,
|
|
219
285
|
fields,
|
|
220
|
-
|
|
286
|
+
defaultValue,
|
|
287
|
+
schema,
|
|
288
|
+
candidType: "tuple",
|
|
221
289
|
}
|
|
222
290
|
}
|
|
223
291
|
|
|
@@ -225,19 +293,32 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
225
293
|
_t: IDL.OptClass<T>,
|
|
226
294
|
ty: IDL.Type<T>,
|
|
227
295
|
label: string
|
|
228
|
-
):
|
|
229
|
-
const
|
|
296
|
+
): OptionalField {
|
|
297
|
+
const name = this.currentName()
|
|
298
|
+
|
|
299
|
+
// For optional, the inner field keeps the same name path
|
|
300
|
+
// because the value replaces null directly (not nested)
|
|
301
|
+
const innerField = ty.accept(this, label) as Field
|
|
302
|
+
|
|
303
|
+
const schema = z.union([
|
|
304
|
+
innerField.schema,
|
|
305
|
+
z.null(),
|
|
306
|
+
z.undefined().transform(() => null),
|
|
307
|
+
z.literal("").transform(() => null),
|
|
308
|
+
])
|
|
230
309
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
) as ArgumentField
|
|
310
|
+
// Helper to get the inner default when enabling the optional
|
|
311
|
+
const getInnerDefault = (): unknown => innerField.defaultValue
|
|
234
312
|
|
|
235
313
|
return {
|
|
236
314
|
type: "optional",
|
|
237
315
|
label,
|
|
238
|
-
|
|
316
|
+
name,
|
|
239
317
|
innerField,
|
|
240
318
|
defaultValue: null,
|
|
319
|
+
schema,
|
|
320
|
+
getInnerDefault,
|
|
321
|
+
candidType: "opt",
|
|
241
322
|
}
|
|
242
323
|
}
|
|
243
324
|
|
|
@@ -245,32 +326,49 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
245
326
|
_t: IDL.VecClass<T>,
|
|
246
327
|
ty: IDL.Type<T>,
|
|
247
328
|
label: string
|
|
248
|
-
):
|
|
249
|
-
const
|
|
329
|
+
): VectorField | BlobField {
|
|
330
|
+
const name = this.currentName()
|
|
250
331
|
|
|
251
332
|
// Check if it's blob (vec nat8)
|
|
252
333
|
const isBlob = ty instanceof IDL.FixedNatClass && ty._bits === 8
|
|
253
334
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
335
|
+
// Item field uses [0] as template path
|
|
336
|
+
const itemField = this.withName("[0]", () =>
|
|
337
|
+
ty.accept(this, `${label}_item`)
|
|
338
|
+
) as Field
|
|
257
339
|
|
|
258
340
|
if (isBlob) {
|
|
341
|
+
const schema = z.union([
|
|
342
|
+
z.string(),
|
|
343
|
+
z.array(z.number()),
|
|
344
|
+
z.instanceof(Uint8Array),
|
|
345
|
+
])
|
|
259
346
|
return {
|
|
260
347
|
type: "blob",
|
|
261
348
|
label,
|
|
262
|
-
|
|
349
|
+
name,
|
|
263
350
|
itemField,
|
|
264
351
|
defaultValue: "",
|
|
352
|
+
schema,
|
|
353
|
+
acceptedFormats: ["hex", "base64", "file"],
|
|
354
|
+
candidType: "blob",
|
|
265
355
|
}
|
|
266
356
|
}
|
|
267
357
|
|
|
358
|
+
const schema = z.array(itemField.schema)
|
|
359
|
+
|
|
360
|
+
// Helper to get a new item with default values
|
|
361
|
+
const getItemDefault = (): unknown => itemField.defaultValue
|
|
362
|
+
|
|
268
363
|
return {
|
|
269
364
|
type: "vector",
|
|
270
365
|
label,
|
|
271
|
-
|
|
366
|
+
name,
|
|
272
367
|
itemField,
|
|
273
368
|
defaultValue: [],
|
|
369
|
+
schema,
|
|
370
|
+
getItemDefault,
|
|
371
|
+
candidType: "vec",
|
|
274
372
|
}
|
|
275
373
|
}
|
|
276
374
|
|
|
@@ -278,16 +376,36 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
278
376
|
_t: IDL.RecClass<T>,
|
|
279
377
|
ty: IDL.ConstructType<T>,
|
|
280
378
|
label: string
|
|
281
|
-
):
|
|
282
|
-
const
|
|
379
|
+
): RecursiveField {
|
|
380
|
+
const name = this.currentName()
|
|
381
|
+
const typeName = ty.name || "RecursiveType"
|
|
382
|
+
|
|
383
|
+
let schema: z.ZodTypeAny
|
|
384
|
+
|
|
385
|
+
if (this.recursiveSchemas.has(typeName)) {
|
|
386
|
+
schema = this.recursiveSchemas.get(typeName)!
|
|
387
|
+
} else {
|
|
388
|
+
schema = z.lazy(() => (ty.accept(this, label) as Field).schema)
|
|
389
|
+
this.recursiveSchemas.set(typeName, schema)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Lazy extraction to prevent infinite loops
|
|
393
|
+
const extract = (): Field =>
|
|
394
|
+
this.withName(name, () => ty.accept(this, label)) as Field
|
|
395
|
+
|
|
396
|
+
// Helper to get inner default (evaluates lazily)
|
|
397
|
+
const getInnerDefault = (): unknown => extract().defaultValue
|
|
283
398
|
|
|
284
399
|
return {
|
|
285
400
|
type: "recursive",
|
|
286
401
|
label,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
extract
|
|
290
|
-
|
|
402
|
+
name,
|
|
403
|
+
typeName,
|
|
404
|
+
extract,
|
|
405
|
+
defaultValue: undefined,
|
|
406
|
+
schema,
|
|
407
|
+
getInnerDefault,
|
|
408
|
+
candidType: "rec",
|
|
291
409
|
}
|
|
292
410
|
}
|
|
293
411
|
|
|
@@ -295,111 +413,198 @@ export class ArgumentFieldVisitor<A = BaseActor> extends IDL.Visitor<
|
|
|
295
413
|
// Primitive Types
|
|
296
414
|
// ════════════════════════════════════════════════════════════════════════
|
|
297
415
|
|
|
298
|
-
public visitPrincipal(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
416
|
+
public visitPrincipal(_t: IDL.PrincipalClass, label: string): PrincipalField {
|
|
417
|
+
const schema = z.custom<Principal>(
|
|
418
|
+
(val) => {
|
|
419
|
+
if (val instanceof Principal) return true
|
|
420
|
+
if (typeof val === "string") {
|
|
421
|
+
try {
|
|
422
|
+
Principal.fromText(val)
|
|
423
|
+
return true
|
|
424
|
+
} catch {
|
|
425
|
+
return false
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return false
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
message: "Invalid Principal format",
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
|
|
302
435
|
return {
|
|
303
436
|
type: "principal",
|
|
304
437
|
label,
|
|
305
|
-
|
|
438
|
+
name: this.currentName(),
|
|
306
439
|
defaultValue: "",
|
|
307
440
|
maxLength: 64,
|
|
308
441
|
minLength: 7,
|
|
442
|
+
schema,
|
|
443
|
+
candidType: "principal",
|
|
444
|
+
ui: {
|
|
445
|
+
placeholder: "aaaaa-aa or full principal ID",
|
|
446
|
+
},
|
|
309
447
|
}
|
|
310
448
|
}
|
|
311
449
|
|
|
312
|
-
public visitText(_t: IDL.TextClass, label: string):
|
|
450
|
+
public visitText(_t: IDL.TextClass, label: string): TextField {
|
|
313
451
|
return {
|
|
314
452
|
type: "text",
|
|
315
453
|
label,
|
|
316
|
-
|
|
454
|
+
name: this.currentName(),
|
|
317
455
|
defaultValue: "",
|
|
456
|
+
schema: z.string().min(1, "Required"),
|
|
457
|
+
candidType: "text",
|
|
458
|
+
ui: {
|
|
459
|
+
placeholder: "Enter text...",
|
|
460
|
+
},
|
|
318
461
|
}
|
|
319
462
|
}
|
|
320
463
|
|
|
321
|
-
public visitBool(_t: IDL.BoolClass, label: string):
|
|
464
|
+
public visitBool(_t: IDL.BoolClass, label: string): BooleanField {
|
|
322
465
|
return {
|
|
323
466
|
type: "boolean",
|
|
324
467
|
label,
|
|
325
|
-
|
|
468
|
+
name: this.currentName(),
|
|
326
469
|
defaultValue: false,
|
|
470
|
+
schema: z.boolean(),
|
|
471
|
+
candidType: "bool",
|
|
327
472
|
}
|
|
328
473
|
}
|
|
329
474
|
|
|
330
|
-
public visitNull(_t: IDL.NullClass, label: string):
|
|
475
|
+
public visitNull(_t: IDL.NullClass, label: string): NullField {
|
|
331
476
|
return {
|
|
332
477
|
type: "null",
|
|
333
478
|
label,
|
|
334
|
-
|
|
479
|
+
name: this.currentName(),
|
|
335
480
|
defaultValue: null,
|
|
481
|
+
schema: z.null(),
|
|
482
|
+
candidType: "null",
|
|
336
483
|
}
|
|
337
484
|
}
|
|
338
485
|
|
|
339
|
-
//
|
|
486
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
487
|
+
// Number Types with Constraints
|
|
488
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
489
|
+
|
|
340
490
|
private visitNumberType(
|
|
341
491
|
label: string,
|
|
342
|
-
candidType: string
|
|
343
|
-
|
|
492
|
+
candidType: string,
|
|
493
|
+
options: {
|
|
494
|
+
unsigned: boolean
|
|
495
|
+
isFloat: boolean
|
|
496
|
+
bits?: number
|
|
497
|
+
min?: string
|
|
498
|
+
max?: string
|
|
499
|
+
}
|
|
500
|
+
): NumberField | TextField {
|
|
501
|
+
let schema = z.string().min(1, "Required")
|
|
502
|
+
|
|
503
|
+
if (options.isFloat) {
|
|
504
|
+
schema = schema.refine((val) => !isNaN(Number(val)), "Must be a number")
|
|
505
|
+
} else if (options.unsigned) {
|
|
506
|
+
schema = schema.regex(/^\d+$/, "Must be a positive number")
|
|
507
|
+
} else {
|
|
508
|
+
schema = schema.regex(/^-?\d+$/, "Must be a number")
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Use "text" type for large numbers (BigInt) to ensure precision and better UI handling
|
|
512
|
+
// Standard number input has issues with large integers
|
|
513
|
+
const isBigInt = !options.isFloat && (!options.bits || options.bits > 32)
|
|
514
|
+
const type = isBigInt ? "text" : "number"
|
|
515
|
+
|
|
516
|
+
if (type === "text") {
|
|
517
|
+
return {
|
|
518
|
+
type: "text",
|
|
519
|
+
label,
|
|
520
|
+
name: this.currentName(),
|
|
521
|
+
defaultValue: "",
|
|
522
|
+
candidType,
|
|
523
|
+
schema,
|
|
524
|
+
ui: {
|
|
525
|
+
placeholder: options.unsigned ? "e.g. 100000" : "e.g. -100000",
|
|
526
|
+
},
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
344
530
|
return {
|
|
345
531
|
type: "number",
|
|
346
532
|
label,
|
|
347
|
-
|
|
533
|
+
name: this.currentName(),
|
|
348
534
|
defaultValue: "",
|
|
349
535
|
candidType,
|
|
536
|
+
schema: schema,
|
|
537
|
+
...options,
|
|
538
|
+
ui: {
|
|
539
|
+
placeholder: options.isFloat ? "0.0" : "0",
|
|
540
|
+
},
|
|
350
541
|
}
|
|
351
542
|
}
|
|
352
543
|
|
|
353
|
-
public visitInt(_t: IDL.IntClass, label: string):
|
|
354
|
-
return this.visitNumberType(label, "int"
|
|
544
|
+
public visitInt(_t: IDL.IntClass, label: string): NumberField | TextField {
|
|
545
|
+
return this.visitNumberType(label, "int", {
|
|
546
|
+
unsigned: false,
|
|
547
|
+
isFloat: false,
|
|
548
|
+
})
|
|
355
549
|
}
|
|
356
550
|
|
|
357
|
-
public visitNat(_t: IDL.NatClass, label: string):
|
|
358
|
-
return this.visitNumberType(label, "nat"
|
|
551
|
+
public visitNat(_t: IDL.NatClass, label: string): NumberField | TextField {
|
|
552
|
+
return this.visitNumberType(label, "nat", {
|
|
553
|
+
unsigned: true,
|
|
554
|
+
isFloat: false,
|
|
555
|
+
})
|
|
359
556
|
}
|
|
360
557
|
|
|
361
|
-
public visitFloat(
|
|
362
|
-
return this.visitNumberType(label,
|
|
558
|
+
public visitFloat(t: IDL.FloatClass, label: string): NumberField {
|
|
559
|
+
return this.visitNumberType(label, `float${t._bits}`, {
|
|
560
|
+
unsigned: false,
|
|
561
|
+
isFloat: true,
|
|
562
|
+
bits: t._bits,
|
|
563
|
+
}) as NumberField
|
|
363
564
|
}
|
|
364
565
|
|
|
365
566
|
public visitFixedInt(
|
|
366
567
|
t: IDL.FixedIntClass,
|
|
367
568
|
label: string
|
|
368
|
-
):
|
|
369
|
-
|
|
569
|
+
): NumberField | TextField {
|
|
570
|
+
const bits = t._bits
|
|
571
|
+
// Calculate min/max for signed integers
|
|
572
|
+
const max = (BigInt(2) ** BigInt(bits - 1) - BigInt(1)).toString()
|
|
573
|
+
const min = (-(BigInt(2) ** BigInt(bits - 1))).toString()
|
|
574
|
+
|
|
575
|
+
return this.visitNumberType(label, `int${bits}`, {
|
|
576
|
+
unsigned: false,
|
|
577
|
+
isFloat: false,
|
|
578
|
+
bits,
|
|
579
|
+
min,
|
|
580
|
+
max,
|
|
581
|
+
})
|
|
370
582
|
}
|
|
371
583
|
|
|
372
584
|
public visitFixedNat(
|
|
373
585
|
t: IDL.FixedNatClass,
|
|
374
586
|
label: string
|
|
375
|
-
):
|
|
376
|
-
|
|
587
|
+
): NumberField | TextField {
|
|
588
|
+
const bits = t._bits
|
|
589
|
+
// Calculate max for unsigned integers
|
|
590
|
+
const max = (BigInt(2) ** BigInt(bits) - BigInt(1)).toString()
|
|
591
|
+
|
|
592
|
+
return this.visitNumberType(label, `nat${bits}`, {
|
|
593
|
+
unsigned: true,
|
|
594
|
+
isFloat: false,
|
|
595
|
+
bits,
|
|
596
|
+
min: "0",
|
|
597
|
+
max,
|
|
598
|
+
})
|
|
377
599
|
}
|
|
378
600
|
|
|
379
|
-
public visitType<T>(_t: IDL.Type<T>, label: string):
|
|
601
|
+
public visitType<T>(_t: IDL.Type<T>, label: string): UnknownField {
|
|
380
602
|
return {
|
|
381
603
|
type: "unknown",
|
|
382
604
|
label,
|
|
383
|
-
|
|
605
|
+
name: this.currentName(),
|
|
384
606
|
defaultValue: undefined,
|
|
607
|
+
schema: z.any(),
|
|
385
608
|
}
|
|
386
609
|
}
|
|
387
610
|
}
|
|
388
|
-
|
|
389
|
-
// ════════════════════════════════════════════════════════════════════════════
|
|
390
|
-
// Legacy Exports (for backward compatibility)
|
|
391
|
-
// ════════════════════════════════════════════════════════════════════════════
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* @deprecated Use ArgumentFieldVisitor instead
|
|
395
|
-
*/
|
|
396
|
-
export { ArgumentFieldVisitor as VisitTanstackField }
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* @deprecated Use ArgumentField instead
|
|
400
|
-
*/
|
|
401
|
-
export type {
|
|
402
|
-
ArgumentField as TanstackAllArgTypes,
|
|
403
|
-
MethodArgumentsMeta as TanstackMethodField,
|
|
404
|
-
ServiceArgumentsMeta as TanstackServiceField,
|
|
405
|
-
}
|