@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.
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 +1 -2
  21. package/dist/visitor/returns/index.d.ts.map +1 -1
  22. package/dist/visitor/returns/index.js +2 -3
  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 +1 -1
  39. package/src/visitor/returns/index.ts +2 -3
  40. 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
- ArgumentField,
5
- RecordArgumentField,
6
- VariantArgumentField,
7
- TupleArgumentField,
8
- OptionalArgumentField,
9
- VectorArgumentField,
10
- BlobArgumentField,
11
- RecursiveArgumentField,
12
- PrincipalArgumentField,
13
- NumberArgumentField,
14
- BooleanArgumentField,
15
- NullArgumentField,
16
- TextArgumentField,
17
- UnknownArgumentField,
18
- MethodArgumentsMeta,
19
- ServiceArgumentsMeta,
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
- * - `path`: Dot-notation path for form binding (e.g., "0.owner")
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
- * // methodMeta.fields = [{ type: "record", fields: [...] }]
52
- * // methodMeta.defaultValues = [{ to: "", amount: "" }]
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
- ArgumentField | MethodArgumentsMeta<A> | ServiceArgumentsMeta<A>
79
+ Field | ArgumentsMeta<A> | ArgumentsServiceMeta<A>
58
80
  > {
59
- private pathStack: string[] = []
81
+ public recursiveSchemas: Map<string, z.ZodTypeAny> = new Map()
60
82
 
61
- private withPath<T>(path: string, fn: () => T): T {
62
- this.pathStack.push(path)
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.pathStack.pop()
94
+ this.nameStack.pop()
67
95
  }
68
96
  }
69
97
 
70
- private currentPath(): string {
71
- return this.pathStack[this.pathStack.length - 1] ?? ""
72
- }
73
-
74
- private childPath(key: string | number): string {
75
- const parent = this.currentPath()
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): ServiceArgumentsMeta<A> {
87
- const result = {} as ServiceArgumentsMeta<A>
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 MethodArgumentsMeta<A>
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
- ): MethodArgumentsMeta<A> {
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.withPath(`[${index}]`, () =>
131
+ return this.withName(`[${index}]`, () =>
107
132
  arg.accept(this, `__arg${index}`)
108
- ) as ArgumentField
133
+ ) as Field
109
134
  })
110
135
 
111
- const defaultValues = fields.map((field) => this.extractDefaultValue(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
- ): RecordArgumentField {
140
- const path = this.currentPath()
141
- const fields: ArgumentField[] = []
142
- const defaultValues: Record<string, unknown> = {}
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.withPath(this.childPath(key), () =>
180
+ const field = this.withName(name ? `.${key}` : key, () =>
146
181
  type.accept(this, key)
147
- ) as ArgumentField
182
+ ) as Field
148
183
 
149
184
  fields.push(field)
150
- defaultValues[key] = this.extractDefaultValue(field)
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
- path,
195
+ name,
157
196
  fields,
158
- defaultValues,
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
- ): VariantArgumentField {
167
- const path = this.currentPath()
168
- const fields: ArgumentField[] = []
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.withPath(this.childPath(key), () =>
216
+ const field = this.withName(`.${key}`, () =>
173
217
  type.accept(this, key)
174
- ) as ArgumentField
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 defaultValues = {
182
- [defaultOption]: this.extractDefaultValue(fields[0]),
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
- path,
246
+ name,
189
247
  fields,
190
248
  options,
191
249
  defaultOption,
192
- defaultValues,
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
- ): TupleArgumentField {
201
- const path = this.currentPath()
202
- const fields: ArgumentField[] = []
203
- const defaultValues: unknown[] = []
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.withPath(this.childPath(index), () =>
270
+ const field = this.withName(`[${index}]`, () =>
208
271
  type.accept(this, `_${index}_`)
209
- ) as ArgumentField
272
+ ) as Field
210
273
 
211
274
  fields.push(field)
212
- defaultValues.push(this.extractDefaultValue(field))
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
- path,
284
+ name,
219
285
  fields,
220
- defaultValues,
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
- ): OptionalArgumentField {
229
- const path = this.currentPath()
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
- const innerField = this.withPath(this.childPath(0), () =>
232
- ty.accept(this, label)
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
- path,
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
- ): VectorArgumentField | BlobArgumentField {
249
- const path = this.currentPath()
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
- const itemField = this.withPath(this.childPath(0), () =>
255
- ty.accept(this, label)
256
- ) as ArgumentField
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
- path,
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
- path,
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
- ): RecursiveArgumentField {
282
- const path = this.currentPath()
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
- path,
288
- // Lazy extraction to prevent infinite loops
289
- extract: () =>
290
- this.withPath(path, () => ty.accept(this, label)) as ArgumentField,
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
- _t: IDL.PrincipalClass,
300
- label: string
301
- ): PrincipalArgumentField {
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
- path: this.currentPath(),
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): TextArgumentField {
450
+ public visitText(_t: IDL.TextClass, label: string): TextField {
313
451
  return {
314
452
  type: "text",
315
453
  label,
316
- path: this.currentPath(),
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): BooleanArgumentField {
464
+ public visitBool(_t: IDL.BoolClass, label: string): BooleanField {
322
465
  return {
323
466
  type: "boolean",
324
467
  label,
325
- path: this.currentPath(),
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): NullArgumentField {
475
+ public visitNull(_t: IDL.NullClass, label: string): NullField {
331
476
  return {
332
477
  type: "null",
333
478
  label,
334
- path: this.currentPath(),
479
+ name: this.currentName(),
335
480
  defaultValue: null,
481
+ schema: z.null(),
482
+ candidType: "null",
336
483
  }
337
484
  }
338
485
 
339
- // Numbers - all use string for display format
486
+ // ════════════════════════════════════════════════════════════════════════
487
+ // Number Types with Constraints
488
+ // ════════════════════════════════════════════════════════════════════════
489
+
340
490
  private visitNumberType(
341
491
  label: string,
342
- candidType: string
343
- ): NumberArgumentField {
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
- path: this.currentPath(),
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): NumberArgumentField {
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): NumberArgumentField {
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(_t: IDL.FloatClass, label: string): NumberArgumentField {
362
- return this.visitNumberType(label, "float")
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
- ): NumberArgumentField {
369
- return this.visitNumberType(label, `int${t._bits}`)
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
- ): NumberArgumentField {
376
- return this.visitNumberType(label, `nat${t._bits}`)
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): UnknownArgumentField {
601
+ public visitType<T>(_t: IDL.Type<T>, label: string): UnknownField {
380
602
  return {
381
603
  type: "unknown",
382
604
  label,
383
- path: this.currentPath(),
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
- }