@ic-reactor/candid 3.0.11-beta.2 → 3.0.13-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 (44) 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/metadata-display-reactor.js +2 -2
  11. package/dist/metadata-display-reactor.js.map +1 -1
  12. package/dist/visitor/arguments/helpers.d.ts +40 -0
  13. package/dist/visitor/arguments/helpers.d.ts.map +1 -0
  14. package/dist/visitor/arguments/helpers.js +81 -0
  15. package/dist/visitor/arguments/helpers.js.map +1 -0
  16. package/dist/visitor/arguments/index.d.ts +64 -42
  17. package/dist/visitor/arguments/index.d.ts.map +1 -1
  18. package/dist/visitor/arguments/index.js +472 -85
  19. package/dist/visitor/arguments/index.js.map +1 -1
  20. package/dist/visitor/arguments/types.d.ts +481 -46
  21. package/dist/visitor/arguments/types.d.ts.map +1 -1
  22. package/dist/visitor/helpers.d.ts +1 -1
  23. package/dist/visitor/helpers.d.ts.map +1 -1
  24. package/dist/visitor/returns/index.d.ts +1 -2
  25. package/dist/visitor/returns/index.d.ts.map +1 -1
  26. package/dist/visitor/returns/index.js +2 -3
  27. package/dist/visitor/returns/index.js.map +1 -1
  28. package/dist/visitor/types.d.ts +2 -3
  29. package/dist/visitor/types.d.ts.map +1 -1
  30. package/dist/visitor/types.js +1 -2
  31. package/dist/visitor/types.js.map +1 -1
  32. package/package.json +6 -3
  33. package/src/display-reactor.ts +4 -6
  34. package/src/index.ts +0 -1
  35. package/src/metadata-display-reactor.ts +8 -8
  36. package/src/visitor/arguments/helpers.ts +104 -0
  37. package/src/visitor/arguments/index.test.ts +544 -52
  38. package/src/visitor/arguments/index.ts +590 -151
  39. package/src/visitor/arguments/schema.test.ts +215 -0
  40. package/src/visitor/arguments/types.ts +554 -62
  41. package/src/visitor/helpers.ts +1 -1
  42. package/src/visitor/returns/index.test.ts +1 -1
  43. package/src/visitor/returns/index.ts +2 -3
  44. package/src/visitor/types.ts +2 -3
@@ -1,4 +1,5 @@
1
1
  import type { BaseActor, FunctionName, FunctionType } from "@ic-reactor/core"
2
+ import * as z from "zod"
2
3
 
3
4
  // ════════════════════════════════════════════════════════════════════════════
4
5
  // Field Type Union
@@ -19,150 +20,641 @@ export type ArgumentFieldType =
19
20
  | "null"
20
21
  | "unknown"
21
22
 
23
+ // ════════════════════════════════════════════════════════════════════════════
24
+ // Component Type Hints
25
+ // ════════════════════════════════════════════════════════════════════════════
26
+
27
+ /**
28
+ * Suggested component type for rendering the field.
29
+ * This eliminates the need for switch statements in the frontend.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * const componentMap = {
34
+ * 'text-input': TextField,
35
+ * 'number-input': NumberField,
36
+ * 'boolean-checkbox': BooleanField,
37
+ * // ...
38
+ * }
39
+ * const Component = componentMap[field.component]
40
+ * return <Component field={field} />
41
+ * ```
42
+ */
43
+ export type FieldComponentType =
44
+ | "record-container"
45
+ | "tuple-container"
46
+ | "variant-select"
47
+ | "optional-toggle"
48
+ | "vector-list"
49
+ | "blob-upload"
50
+ | "principal-input"
51
+ | "text-input"
52
+ | "number-input"
53
+ | "boolean-checkbox"
54
+ | "null-hidden"
55
+ | "recursive-lazy"
56
+ | "unknown-fallback"
57
+
58
+ // ════════════════════════════════════════════════════════════════════════════
59
+ // Render Hints for UI Rendering Strategy
60
+ // ════════════════════════════════════════════════════════════════════════════
61
+
62
+ /**
63
+ * Input type hints for HTML input elements.
64
+ * Used by primitive fields to suggest the appropriate input type.
65
+ */
66
+ export type InputType =
67
+ | "text"
68
+ | "number"
69
+ | "checkbox"
70
+ | "select"
71
+ | "file"
72
+ | "textarea"
73
+
74
+ /**
75
+ * Rendering hints for the UI.
76
+ * Eliminates the need for frontend to maintain COMPLEX_TYPES arrays.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * // Frontend no longer needs:
81
+ * // const COMPLEX_TYPES = ["record", "tuple", "variant", "vector", "optional"]
82
+ *
83
+ * // Instead use:
84
+ * if (field.renderHint.isCompound) {
85
+ * return <CompoundFieldRenderer field={field} />
86
+ * }
87
+ * return <PrimitiveInput field={field} />
88
+ * ```
89
+ */
90
+ export interface RenderHint {
91
+ /** Whether this field has its own container/card styling (compound types) */
92
+ isCompound: boolean
93
+ /** Whether this is a leaf input (primitive types) */
94
+ isPrimitive: boolean
95
+ /** Suggested input type for HTML input elements */
96
+ inputType?: InputType
97
+ /** Description or help text for the field (derived from Candid) */
98
+ description?: string
99
+ }
100
+
101
+ // ════════════════════════════════════════════════════════════════════════════
102
+ // Primitive Input Props
103
+ // ════════════════════════════════════════════════════════════════════════════
104
+
105
+ /**
106
+ * Pre-computed HTML input props for primitive fields.
107
+ * Can be spread directly onto an input element.
108
+ *
109
+ * @example
110
+ * ```tsx
111
+ * <input {...field.inputProps} value={value} onChange={handleChange} />
112
+ * ```
113
+ */
114
+ export interface PrimitiveInputProps {
115
+ /** HTML input type */
116
+ type?: "text" | "number" | "checkbox"
117
+ /** Placeholder text */
118
+ placeholder?: string
119
+ /** Minimum value for number inputs */
120
+ min?: string | number
121
+ /** Maximum value for number inputs */
122
+ max?: string | number
123
+ /** Step value for number inputs */
124
+ step?: string | number
125
+ /** Pattern for text inputs */
126
+ pattern?: string
127
+ /** Input mode for virtual keyboards */
128
+ inputMode?: "text" | "numeric" | "decimal"
129
+ /** Autocomplete hint */
130
+ autoComplete?: string
131
+ /** Whether to check spelling */
132
+ spellCheck?: boolean
133
+ /** Minimum length for text inputs */
134
+ minLength?: number
135
+ /** Maximum length for text inputs */
136
+ maxLength?: number
137
+ }
138
+
22
139
  // ════════════════════════════════════════════════════════════════════════════
23
140
  // Base Field Interface
24
141
  // ════════════════════════════════════════════════════════════════════════════
25
142
 
26
- export interface ArgumentFieldBase {
143
+ export interface FieldBase<TValue = unknown> {
27
144
  /** The field type */
28
145
  type: ArgumentFieldType
29
- /** Human-readable label from Candid */
146
+ /** Raw label from Candid: "__arg0", "_0_" */
30
147
  label: string
31
- /** Dot-notation path for form binding (e.g., "0.owner", "[0].to") */
32
- path: string
148
+ /**
149
+ * Pre-formatted display label for UI rendering.
150
+ * Transforms raw labels into human-readable format.
151
+ *
152
+ * @example
153
+ * "__arg0" => "Arg 0"
154
+ * "_0_" => "Item 0"
155
+ * "created_at_time" => "Created At Time"
156
+ */
157
+ displayLabel: string
158
+ /**
159
+ * Form field name path for binding.
160
+ * Uses bracket notation for array indices: `[0]`, `args[0].owner`, `tags[1]`
161
+ * Compatible with TanStack Form's `form.Field` name prop.
162
+ *
163
+ * @example
164
+ * ```tsx
165
+ * <form.Field name={field.name}>
166
+ * {(fieldApi) => <input {...} />}
167
+ * </form.Field>
168
+ * ```
169
+ */
170
+ name: string
171
+ /**
172
+ * Suggested component type for rendering this field.
173
+ * Eliminates the need for switch statements in the frontend.
174
+ */
175
+ component: FieldComponentType
176
+ /**
177
+ * Rendering hints for UI strategy.
178
+ * Use this to determine if the field needs a container or is a simple input.
179
+ */
180
+ renderHint: RenderHint
181
+ /** Zod schema for field validation */
182
+ schema: z.ZodTypeAny
183
+ /** Default value for the field */
184
+ defaultValue: TValue
185
+ /** Original Candid type name for reference */
186
+ candidType?: string
33
187
  }
34
188
 
35
189
  // ════════════════════════════════════════════════════════════════════════════
36
190
  // Compound Types
37
191
  // ════════════════════════════════════════════════════════════════════════════
38
192
 
39
- export interface RecordArgumentField extends ArgumentFieldBase {
193
+ export interface RecordField extends FieldBase<Record<string, unknown>> {
40
194
  type: "record"
41
- fields: ArgumentField[]
42
- defaultValues: Record<string, unknown>
195
+ /** Child fields in the record */
196
+ fields: Field[]
197
+ /** Map of field label to its metadata for quick lookup */
198
+ fieldMap: Map<string, Field>
43
199
  }
44
200
 
45
- export interface VariantArgumentField extends ArgumentFieldBase {
201
+ export interface VariantField extends FieldBase<Record<string, unknown>> {
46
202
  type: "variant"
47
- fields: ArgumentField[]
203
+ /** All variant option fields */
204
+ fields: Field[]
205
+ /** List of variant option names */
48
206
  options: string[]
207
+ /** Default selected option */
49
208
  defaultOption: string
50
- defaultValues: Record<string, unknown>
209
+ /** Map of option name to its field metadata */
210
+ optionMap: Map<string, Field>
211
+ /**
212
+ * Get default value for a specific option.
213
+ * Useful when switching between variant options.
214
+ *
215
+ * @example
216
+ * ```tsx
217
+ * const handleOptionChange = (newOption: string) => {
218
+ * const newDefault = field.getOptionDefault(newOption)
219
+ * fieldApi.handleChange(newDefault)
220
+ * }
221
+ * ```
222
+ */
223
+ getOptionDefault: (option: string) => Record<string, unknown>
224
+ /**
225
+ * Get the field for a specific option.
226
+ *
227
+ * @example
228
+ * ```tsx
229
+ * const transferField = field.getField("Transfer")
230
+ * ```
231
+ */
232
+ getField: (option: string) => Field
233
+ /**
234
+ * Get the currently selected option from a value.
235
+ * Returns the first valid key found, or the default option.
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * const selectedOption = field.getSelectedOption(currentValue)
240
+ * // { Transfer: {...} } => "Transfer"
241
+ * ```
242
+ */
243
+ getSelectedOption: (value: Record<string, unknown>) => string
244
+ /**
245
+ * Get the selected field from a value.
246
+ * Combines getSelectedOption and getField for convenience.
247
+ *
248
+ * @example
249
+ * ```tsx
250
+ * // Current (verbose):
251
+ * const validKeys = Object.keys(currentValue).filter(k => field.options.includes(k))
252
+ * const selected = validKeys[0] ?? field.options[0]
253
+ * const selectedIndex = Math.max(0, field.options.indexOf(selected))
254
+ * const selectedField = field.fields[selectedIndex]
255
+ *
256
+ * // Proposed (simple):
257
+ * const selectedField = field.getSelectedField(currentValue)
258
+ * ```
259
+ */
260
+ getSelectedField: (value: Record<string, unknown>) => Field
51
261
  }
52
262
 
53
- export interface TupleArgumentField extends ArgumentFieldBase {
263
+ export interface TupleField extends FieldBase<unknown[]> {
54
264
  type: "tuple"
55
- fields: ArgumentField[]
56
- defaultValues: unknown[]
265
+ /** Tuple element fields in order */
266
+ fields: Field[]
57
267
  }
58
268
 
59
- export interface OptionalArgumentField extends ArgumentFieldBase {
269
+ export interface OptionalField extends FieldBase<null> {
60
270
  type: "optional"
61
- innerField: ArgumentField
62
- defaultValue: null
271
+ /** The inner field when value is present */
272
+ innerField: Field
273
+ /**
274
+ * Get default value when enabling the optional.
275
+ * Returns the inner field's default value.
276
+ *
277
+ * @example
278
+ * ```tsx
279
+ * const handleToggle = (enabled: boolean) => {
280
+ * if (enabled) {
281
+ * fieldApi.handleChange(field.getInnerDefault())
282
+ * } else {
283
+ * fieldApi.handleChange(null)
284
+ * }
285
+ * }
286
+ * ```
287
+ */
288
+ getInnerDefault: () => unknown
289
+ /**
290
+ * Check if a value represents an enabled optional.
291
+ * Returns true if the value is not null or undefined.
292
+ *
293
+ * @example
294
+ * ```tsx
295
+ * // Current:
296
+ * const enabled = fieldApi.state.value !== null && typeof fieldApi.state.value !== "undefined"
297
+ *
298
+ * // Proposed:
299
+ * const enabled = field.isEnabled(fieldApi.state.value)
300
+ * ```
301
+ */
302
+ isEnabled: (value: unknown) => boolean
63
303
  }
64
304
 
65
- export interface VectorArgumentField extends ArgumentFieldBase {
305
+ export interface VectorField extends FieldBase<unknown[]> {
66
306
  type: "vector"
67
- itemField: ArgumentField
68
- defaultValue: []
307
+ /** Template field for vector items */
308
+ itemField: Field
309
+ /**
310
+ * Get a new item with default values.
311
+ * Used when adding items to the vector.
312
+ *
313
+ * @example
314
+ * ```tsx
315
+ * <button onClick={() => fieldApi.pushValue(field.getItemDefault())}>
316
+ * Add Item
317
+ * </button>
318
+ * ```
319
+ */
320
+ getItemDefault: () => unknown
321
+ /**
322
+ * Create a properly configured item field for a specific index.
323
+ * Handles name path and label generation.
324
+ *
325
+ * @example
326
+ * ```tsx
327
+ * // Current:
328
+ * renderField({
329
+ * ...field.itemField,
330
+ * label: itemLabel,
331
+ * name: itemFieldName
332
+ * })
333
+ *
334
+ * // Proposed:
335
+ * const itemField = field.createItemField(index, { label: itemLabel })
336
+ * renderField(itemField)
337
+ * ```
338
+ */
339
+ createItemField: (index: number, overrides?: { label?: string }) => Field
340
+ }
341
+
342
+ /**
343
+ * Blob field size limits.
344
+ */
345
+ export interface BlobLimits {
346
+ /** Maximum bytes when entering as hex (e.g., 512 bytes) */
347
+ maxHexBytes: number
348
+ /** Maximum file size in bytes (e.g., 2MB ICP limit) */
349
+ maxFileBytes: number
350
+ /** Maximum hex display length before truncation */
351
+ maxHexDisplayLength: number
69
352
  }
70
353
 
71
- export interface BlobArgumentField extends ArgumentFieldBase {
354
+ /**
355
+ * Validation result for blob input.
356
+ */
357
+ export interface BlobValidationResult {
358
+ /** Whether the input is valid */
359
+ valid: boolean
360
+ /** Error message if invalid */
361
+ error?: string
362
+ }
363
+
364
+ export interface BlobField extends FieldBase<string> {
72
365
  type: "blob"
73
- itemField: ArgumentField
74
- /** Default is empty hex string */
75
- defaultValue: string
366
+ /** Item field for individual bytes (nat8) */
367
+ itemField: Field
368
+ /** Accepted input formats */
369
+ acceptedFormats: ("hex" | "base64" | "file")[]
370
+ /** Size limits for blob input */
371
+ limits: BlobLimits
372
+ /**
373
+ * Normalize hex input (remove 0x prefix, lowercase, etc.)
374
+ *
375
+ * @example
376
+ * ```tsx
377
+ * const normalized = field.normalizeHex("0xDEADBEEF")
378
+ * // => "deadbeef"
379
+ * ```
380
+ */
381
+ normalizeHex: (input: string) => string
382
+ /**
383
+ * Validate blob input value.
384
+ *
385
+ * @example
386
+ * ```tsx
387
+ * const result = field.validateInput(value)
388
+ * if (!result.valid) {
389
+ * setError(result.error)
390
+ * }
391
+ * ```
392
+ */
393
+ validateInput: (value: string | Uint8Array) => BlobValidationResult
76
394
  }
77
395
 
78
- export interface RecursiveArgumentField extends ArgumentFieldBase {
396
+ export interface RecursiveField extends FieldBase<undefined> {
79
397
  type: "recursive"
398
+ /** Type name for the recursive type */
399
+ typeName: string
80
400
  /** Lazily extract the inner field to prevent infinite loops */
81
- extract: () => ArgumentField
401
+ extract: () => Field
402
+ /**
403
+ * Get default value for the recursive type.
404
+ * Evaluates the inner type on demand.
405
+ */
406
+ getInnerDefault: () => unknown
82
407
  }
83
408
 
84
409
  // ════════════════════════════════════════════════════════════════════════════
85
410
  // Primitive Types
86
411
  // ════════════════════════════════════════════════════════════════════════════
87
412
 
88
- export interface PrincipalArgumentField extends ArgumentFieldBase {
413
+ export interface PrincipalField extends FieldBase<string> {
89
414
  type: "principal"
90
- /** Display format: string */
91
- defaultValue: string
92
415
  maxLength: number
93
416
  minLength: number
417
+ /**
418
+ * Pre-computed HTML input props for direct spreading.
419
+ * @example
420
+ * ```tsx
421
+ * <input {...field.inputProps} value={value} onChange={handleChange} />
422
+ * ```
423
+ */
424
+ inputProps: PrimitiveInputProps
94
425
  }
95
426
 
96
- export interface NumberArgumentField extends ArgumentFieldBase {
427
+ export interface NumberField extends FieldBase<string> {
97
428
  type: "number"
98
- /** Display format: string (for bigint compatibility) */
99
- defaultValue: string
100
- /** Original Candid type: nat, int, nat64, etc. */
429
+ /**
430
+ * Original Candid type: nat, int, nat8, nat16, nat32, nat64, int8, int16, int32, int64, float32, float64
431
+ */
101
432
  candidType: string
433
+ /** Whether this is an unsigned type */
434
+ unsigned: boolean
435
+ /** Whether this is a floating point type */
436
+ isFloat: boolean
437
+ /** Bit width if applicable (8, 16, 32, 64, or undefined for unbounded) */
438
+ bits?: number
439
+ /** Minimum value constraint (for bounded types) */
440
+ min?: string
441
+ /** Maximum value constraint (for bounded types) */
442
+ max?: string
443
+ /**
444
+ * Pre-computed HTML input props for direct spreading.
445
+ * @example
446
+ * ```tsx
447
+ * <input {...field.inputProps} value={value} onChange={handleChange} />
448
+ * ```
449
+ */
450
+ inputProps: PrimitiveInputProps
102
451
  }
103
452
 
104
- export interface TextArgumentField extends ArgumentFieldBase {
453
+ export interface TextField extends FieldBase<string> {
105
454
  type: "text"
106
- defaultValue: string
455
+ /** Minimum length constraint */
456
+ minLength?: number
457
+ /** Maximum length constraint */
458
+ maxLength?: number
459
+ /** Whether to render as multiline textarea */
460
+ multiline?: boolean
461
+ /**
462
+ * Pre-computed HTML input props for direct spreading.
463
+ * @example
464
+ * ```tsx
465
+ * <input {...field.inputProps} value={value} onChange={handleChange} />
466
+ * ```
467
+ */
468
+ inputProps: PrimitiveInputProps
107
469
  }
108
470
 
109
- export interface BooleanArgumentField extends ArgumentFieldBase {
471
+ export interface BooleanField extends FieldBase<boolean> {
110
472
  type: "boolean"
111
- defaultValue: boolean
473
+ /**
474
+ * Pre-computed HTML input props for direct spreading.
475
+ * @example
476
+ * ```tsx
477
+ * <input {...field.inputProps} checked={value} onChange={handleChange} />
478
+ * ```
479
+ */
480
+ inputProps: PrimitiveInputProps
112
481
  }
113
482
 
114
- export interface NullArgumentField extends ArgumentFieldBase {
483
+ export interface NullField extends FieldBase<null> {
115
484
  type: "null"
116
- defaultValue: null
117
485
  }
118
486
 
119
- export interface UnknownArgumentField extends ArgumentFieldBase {
487
+ export interface UnknownField extends FieldBase<undefined> {
120
488
  type: "unknown"
121
- defaultValue: undefined
122
489
  }
123
490
 
124
491
  // ════════════════════════════════════════════════════════════════════════════
125
492
  // Union Type
126
493
  // ════════════════════════════════════════════════════════════════════════════
127
494
 
128
- export type ArgumentField =
129
- | RecordArgumentField
130
- | VariantArgumentField
131
- | TupleArgumentField
132
- | OptionalArgumentField
133
- | VectorArgumentField
134
- | BlobArgumentField
135
- | RecursiveArgumentField
136
- | PrincipalArgumentField
137
- | NumberArgumentField
138
- | TextArgumentField
139
- | BooleanArgumentField
140
- | NullArgumentField
141
- | UnknownArgumentField
495
+ export type Field =
496
+ | RecordField
497
+ | VariantField
498
+ | TupleField
499
+ | OptionalField
500
+ | VectorField
501
+ | BlobField
502
+ | RecursiveField
503
+ | PrincipalField
504
+ | NumberField
505
+ | TextField
506
+ | BooleanField
507
+ | NullField
508
+ | UnknownField
142
509
 
143
510
  // ════════════════════════════════════════════════════════════════════════════
144
- // Method & Service Level
511
+ // Form Metadata - TanStack Form Integration
145
512
  // ════════════════════════════════════════════════════════════════════════════
146
513
 
147
- export interface MethodArgumentsMeta<
514
+ /**
515
+ * Form metadata for a Candid method.
516
+ * Contains all information needed to create a TanStack Form instance.
517
+ *
518
+ * @example
519
+ * ```tsx
520
+ * import { useForm } from '@tanstack/react-form'
521
+ *
522
+ * function MethodForm({ meta }: { meta: FormMeta }) {
523
+ * const form = useForm({
524
+ * ...meta.formOptions,
525
+ * onSubmit: async ({ value }) => {
526
+ * await actor[meta.functionName](...value)
527
+ * }
528
+ * })
529
+ *
530
+ * return (
531
+ * <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>
532
+ * {meta.fields.map(field => (
533
+ * <form.Field key={field.name} name={field.name}>
534
+ * {(fieldApi) => <DynamicInput field={field} fieldApi={fieldApi} />}
535
+ * </form.Field>
536
+ * ))}
537
+ * <button type="submit">Submit</button>
538
+ * </form>
539
+ * )
540
+ * }
541
+ * ```
542
+ */
543
+ export interface ArgumentsMeta<
148
544
  A = BaseActor,
149
545
  Name extends FunctionName<A> = FunctionName<A>,
150
546
  > {
547
+ /** Whether this is a "query" or "update" function */
151
548
  functionType: FunctionType
549
+ /** The function name */
152
550
  functionName: Name
153
- fields: ArgumentField[]
551
+ /** Argument field definitions for rendering */
552
+ fields: Field[]
553
+ /** Default values for all arguments (as a tuple) */
554
+ defaultValues: unknown[]
555
+ /** Combined Zod schema for all arguments */
556
+ schema: z.ZodTuple<[z.ZodTypeAny, ...z.ZodTypeAny[]]>
557
+ /** Number of arguments */
558
+ argCount: number
559
+ /** Whether the function takes no arguments */
560
+ isNoArgs: boolean
561
+ }
562
+
563
+ /**
564
+ * Options that can be spread into useForm().
565
+ * Pre-configured with defaultValues and validators.
566
+ */
567
+ export interface FormOptions {
568
+ /** Initial form values */
154
569
  defaultValues: unknown[]
570
+ /** Validators using the Zod schema */
571
+ validators: {
572
+ onChange: z.ZodTypeAny
573
+ onBlur: z.ZodTypeAny
574
+ }
155
575
  }
156
576
 
157
- export type ServiceArgumentsMeta<A = BaseActor> = {
158
- [K in FunctionName<A>]: MethodArgumentsMeta<A, K>
577
+ /**
578
+ * Service-level form metadata.
579
+ * Maps each method name to its FormMeta.
580
+ */
581
+ export type ArgumentsServiceMeta<A = BaseActor> = {
582
+ [K in FunctionName<A>]: ArgumentsMeta<A, K>
159
583
  }
160
584
 
161
585
  // ════════════════════════════════════════════════════════════════════════════
162
- // Type Utilities
586
+ // Type Utilities & Guards
163
587
  // ════════════════════════════════════════════════════════════════════════════
164
588
 
165
- export type ArgumentFieldByType<T extends ArgumentFieldType> = Extract<
166
- ArgumentField,
589
+ /** Extract a specific field type */
590
+ export type FieldByType<T extends ArgumentFieldType> = Extract<
591
+ Field,
167
592
  { type: T }
168
593
  >
594
+
595
+ /**
596
+ * Props type helper for field components.
597
+ * Use this to type your field components for better DX.
598
+ *
599
+ * @example
600
+ * ```tsx
601
+ * const VariantField: React.FC<FieldProps<'variant'>> = ({ field, renderField }) => {
602
+ * // field is properly typed as VariantField
603
+ * return (
604
+ * <div>
605
+ * <select>{field.options.map(opt => ...)}</select>
606
+ * {renderField?.(field.getSelectedField(currentValue))}
607
+ * </div>
608
+ * )
609
+ * }
610
+ * ```
611
+ */
612
+ export type FieldProps<T extends ArgumentFieldType> = {
613
+ field: FieldByType<T>
614
+ renderField?: (child: Field) => React.ReactNode
615
+ }
616
+
617
+ /** Compound field types that contain other fields */
618
+ export type CompoundField =
619
+ | RecordField
620
+ | VariantField
621
+ | TupleField
622
+ | OptionalField
623
+ | VectorField
624
+ | RecursiveField
625
+
626
+ /** Primitive field types */
627
+ export type PrimitiveField =
628
+ | PrincipalField
629
+ | NumberField
630
+ | TextField
631
+ | BooleanField
632
+ | NullField
633
+
634
+ /**
635
+ * A complete mapping of component types to React components.
636
+ * Use this type when defining your component map.
637
+ *
638
+ * @example
639
+ * ```tsx
640
+ * const componentMap: ComponentMap<typeof MyTextInput, typeof MyNumberInput, ...> = {
641
+ * 'text-input': MyTextInput,
642
+ * 'number-input': MyNumberInput,
643
+ * // ...
644
+ * }
645
+ * ```
646
+ */
647
+ export type ComponentMap<
648
+ TComponents extends Record<FieldComponentType, unknown>,
649
+ > = {
650
+ [K in FieldComponentType]: TComponents[K]
651
+ }
652
+
653
+ /**
654
+ * Get the component type for a given field component type.
655
+ * Useful for typing dynamic component lookups.
656
+ */
657
+ export type GetComponentType<
658
+ TMap extends Partial<Record<FieldComponentType, unknown>>,
659
+ TKey extends FieldComponentType,
660
+ > = TKey extends keyof TMap ? TMap[TKey] : never