@ic-reactor/candid 3.0.11-beta.1 → 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 +3 -3
  21. package/dist/visitor/returns/index.d.ts.map +1 -1
  22. package/dist/visitor/returns/index.js +54 -15
  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 +163 -1
  39. package/src/visitor/returns/index.ts +62 -16
  40. package/src/visitor/types.ts +2 -3
@@ -1,3 +1,2 @@
1
- export { IDL } from "@icp-sdk/core/candid";
2
- export { Principal } from "@icp-sdk/core/principal";
1
+ export {};
3
2
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/visitor/types.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/visitor/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/candid",
3
- "version": "3.0.11-beta.1",
3
+ "version": "3.0.12-beta.0",
4
4
  "description": "IC Reactor Candid Adapter - Fetch and parse Candid definitions from Internet Computer canisters",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -42,10 +42,13 @@
42
42
  "author": "Behrad Deylami",
43
43
  "license": "MIT",
44
44
  "dependencies": {
45
- "@noble/hashes": "^1.8.0",
46
- "@ic-reactor/core": "^3.0.11-beta.1"
45
+ "@noble/hashes": "^2.0.1",
46
+ "zod": "^4.3.5",
47
+ "@ic-reactor/core": "^3.0.12-beta.0"
47
48
  },
48
49
  "peerDependencies": {
50
+ "zod": "^4.3.5",
51
+ "@noble/hashes": "^2.0.0",
49
52
  "@icp-sdk/core": "^5.0.0"
50
53
  },
51
54
  "peerDependenciesMeta": {},
@@ -1,19 +1,17 @@
1
- import type {
2
- BaseActor,
3
- DisplayReactorParameters,
4
- TransformKey,
5
- } from "@ic-reactor/core"
6
1
  import type {
7
2
  CandidDisplayReactorParameters,
8
3
  DynamicMethodOptions,
9
4
  } from "./types"
5
+ import { CandidAdapter } from "./adapter"
10
6
 
11
7
  import {
8
+ BaseActor,
9
+ DisplayReactorParameters,
10
+ TransformKey,
12
11
  DisplayReactor,
13
12
  didToDisplayCodec,
14
13
  didTypeFromArray,
15
14
  } from "@ic-reactor/core"
16
- import { CandidAdapter } from "./adapter"
17
15
  import { IDL } from "@icp-sdk/core/candid"
18
16
 
19
17
  // ============================================================================
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./adapter"
2
2
  export * from "./types"
3
3
  export * from "./visitor"
4
- export type { ResolvedNode } from "./visitor/returns/types"
5
4
  export * from "./constants"
6
5
  export * from "./utils"
7
6
  export * from "./reactor"
@@ -10,8 +10,8 @@ import type {
10
10
  } from "./types"
11
11
  import {
12
12
  ArgumentFieldVisitor,
13
- MethodArgumentsMeta,
14
- ServiceArgumentsMeta,
13
+ ArgumentsMeta,
14
+ ArgumentsServiceMeta,
15
15
  } from "./visitor/arguments"
16
16
  import {
17
17
  MethodMeta,
@@ -50,7 +50,7 @@ export class MetadataDisplayReactor<A = BaseActor> extends CandidDisplayReactor<
50
50
  public override readonly transform = "metadata" as const
51
51
 
52
52
  // Metadata storage
53
- private argumentMeta: ServiceArgumentsMeta<A> | null = null
53
+ private argumentMeta: ArgumentsServiceMeta<A> | null = null
54
54
  private resultMeta: ServiceMeta<A> | null = null
55
55
 
56
56
  // Visitors (stateless, can be reused)
@@ -86,7 +86,7 @@ export class MetadataDisplayReactor<A = BaseActor> extends CandidDisplayReactor<
86
86
  this.argumentMeta = service.accept(
87
87
  MetadataDisplayReactor.argVisitor,
88
88
  null as any
89
- ) as ServiceArgumentsMeta<A>
89
+ ) as ArgumentsServiceMeta<A>
90
90
 
91
91
  // Generate result metadata
92
92
  this.resultMeta = service.accept(
@@ -104,7 +104,7 @@ export class MetadataDisplayReactor<A = BaseActor> extends CandidDisplayReactor<
104
104
  */
105
105
  public getArgumentMeta<M extends FunctionName<A>>(
106
106
  methodName: M
107
- ): MethodArgumentsMeta<A, M> | undefined {
107
+ ): ArgumentsMeta<A, M> | undefined {
108
108
  return this.argumentMeta?.[methodName]
109
109
  }
110
110
 
@@ -121,7 +121,7 @@ export class MetadataDisplayReactor<A = BaseActor> extends CandidDisplayReactor<
121
121
  /**
122
122
  * Get all argument metadata.
123
123
  */
124
- public getAllArgumentMeta(): ServiceArgumentsMeta<A> | null {
124
+ public getAllArgumentMeta(): ArgumentsServiceMeta<A> | null {
125
125
  return this.argumentMeta
126
126
  }
127
127
 
@@ -0,0 +1,230 @@
1
+ # Argument Field Visitor
2
+
3
+ The `ArgumentFieldVisitor` traverses Candid IDL types to generate two things:
4
+
5
+ 1. **Field Metadata**: Structure, labels, names (for form binding), and default values for rendering form fields.
6
+ 2. **Validation Schema**: A Zod schema for validating form inputs.
7
+
8
+ ## Usage
9
+
10
+ ### 1. Initialize the Visitor
11
+
12
+ ```typescript
13
+ import { ArgumentFieldVisitor } from "@ic-reactor/candid"
14
+ import { IDL } from "@icp-sdk/core/candid"
15
+
16
+ const visitor = new ArgumentFieldVisitor()
17
+ ```
18
+
19
+ ### 2. Generate Metadata & Schema
20
+
21
+ You can visit a single function or an entire service.
22
+
23
+ #### For a Service
24
+
25
+ ```typescript
26
+ const serviceMeta = visitor.visitService(idlFactory({ IDL }))
27
+ const transferMeta = serviceMeta["icrc1_transfer"]
28
+
29
+ console.log(transferMeta)
30
+ // Output:
31
+ // {
32
+ // functionName: "icrc1_transfer",
33
+ // functionType: "update",
34
+ // fields: [...], // Field definitions for rendering
35
+ // defaultValues: [...], // Default values for the form (array of argument defaults)
36
+ // schema: ZodSchema, // Zod schema for validation
37
+ // argCount: 1, // Number of arguments
38
+ // isNoArgs: false // Whether the function takes no arguments
39
+ // }
40
+ ```
41
+
42
+ #### For a Single Function
43
+
44
+ ```typescript
45
+ const funcType = IDL.Func([IDL.Text, IDL.Nat], [], [])
46
+ const meta = visitor.visitFunc(funcType, "myMethod")
47
+ ```
48
+
49
+ ### 3. Field Properties
50
+
51
+ Each field in `meta.fields` has the following properties:
52
+
53
+ ```typescript
54
+ {
55
+ type: "text" | "number" | "boolean" | "principal" | "record" | "variant" | ...,
56
+ label: "fieldName", // Human-readable label
57
+ name: "[0].field.nested", // TanStack Form compatible path
58
+ defaultValue: ..., // Default value for this field
59
+ schema: ZodSchema, // Zod schema for this field
60
+ candidType: "text", // Original Candid type
61
+ ui: { // Optional UI hints
62
+ placeholder: "e.g. 100",
63
+ },
64
+ // Type-specific properties:
65
+ // - For "number" fields (Nat8, Int32, Float): min, max, unsigned, isFloat, bits
66
+ // - For "text" fields (Nat, Int, Nat64): (handled as text for BigInt support)
67
+ // - For variants: options, optionMap, getOptionDefault()
68
+ // - For vectors: itemField, getItemDefault()
69
+ // - For optionals: innerField, getInnerDefault()
70
+ // - For records: fields, fieldMap
71
+ }
72
+ ```
73
+
74
+ ### 4. Special Handling & Validation
75
+
76
+ #### BigInts as Text
77
+
78
+ Large integer types (`Nat`, `Int`, `Nat64`, `Int64`, `Nat32` > 32-bit representations) are generated with `type: "text"`.
79
+
80
+ - **Reason**: Standard JavaScript numbers lose precision for values > `2^53 - 1`. HTML number inputs can be unreliable for large integers.
81
+ - **Validation**: The Zod schema strictly validates these as **strings containing only digits** (or sign for signed types).
82
+ - **Label**: They retain their `candidType` (e.g. `nat`) for reference.
83
+
84
+ #### Strict Validation
85
+
86
+ - **Required Fields**: Text and Number fields include `.min(1, "Required")`. Empty strings are rejected.
87
+ - **Integers**: Regex validation ensures only digits (no decimals).
88
+ - **Floats**: Float32/Float64 allow decimal points (e.g., `123.1`) and are validated using standard `!isNaN(Number(val))`.
89
+ - **Principals**: Validated using `Principal.fromText()`. Empty strings are rejected.
90
+
91
+ #### Optional Fields
92
+
93
+ - **Behavior**: Optional fields (`Opt`) wrap the inner schema.
94
+ - **Empty Handling**: An empty string input (`""`) is automatically transformed to `null` (Candid `null` / `None`), ensuring optional fields can be cleared.
95
+
96
+ ### 5. Integration with TanStack Form
97
+
98
+ The visitor is optimized for standard form libraries like TanStack Form.
99
+
100
+ ```tsx
101
+ import { useForm } from "@tanstack/react-form"
102
+
103
+ function MethodForm({ meta }) {
104
+ const form = useForm({
105
+ defaultValues: meta.defaultValues,
106
+ validators: {
107
+ onChange: meta.schema, // Use generated Zod schema for validation
108
+ },
109
+ onSubmit: async ({ value }) => {
110
+ console.log("Structured Data:", value)
111
+ // value is ready to be passed to strict Candid adapters
112
+ },
113
+ })
114
+
115
+ return (
116
+ <form
117
+ onSubmit={(e) => {
118
+ e.preventDefault()
119
+ e.stopPropagation()
120
+ form.handleSubmit()
121
+ }}
122
+ >
123
+ {meta.fields.map((field) => (
124
+ <form.Field key={field.name} name={field.name}>
125
+ {(fieldApi) => (
126
+ <div>
127
+ <label>{field.label}</label>
128
+ <input
129
+ type={
130
+ field.type === "text" || field.type === "principal"
131
+ ? "text"
132
+ : field.type
133
+ }
134
+ value={fieldApi.state.value}
135
+ onChange={(e) => fieldApi.handleChange(e.target.value)}
136
+ placeholder={field.ui?.placeholder}
137
+ />
138
+ {fieldApi.state.meta.errors.map((err) => (
139
+ <span key={err} className="error">
140
+ {err}
141
+ </span>
142
+ ))}
143
+ </div>
144
+ )}
145
+ </form.Field>
146
+ ))}
147
+ <button type="submit">Submit</button>
148
+ </form>
149
+ )
150
+ }
151
+ ```
152
+
153
+ ### 6. Dynamic Fields
154
+
155
+ For **Vectors** and **Variants**, you can access helper paths dynamically:
156
+
157
+ - **Vector**: Field name `items` -> Item name `items[0]`, `items[1]`.
158
+ - **Record**: Field name `user` -> Nested `user.name`.
159
+
160
+ The `name` property in the metadata is pre-calculated to match this structure (e.g., `[0].args.user.name` if it's the first argument).
161
+
162
+ ### 7. Working with Vectors (Arrays)
163
+
164
+ Use helper methods like `getItemDefault()` to manage array items.
165
+
166
+ ```tsx
167
+ function VectorField({ field, form }) {
168
+ return (
169
+ <form.Field name={field.name} mode="array">
170
+ {(arrayFieldApi) => (
171
+ <div>
172
+ {arrayFieldApi.state.value.map((_, index) => (
173
+ /* Render items using field.name + [index] */
174
+ ))}
175
+ <button
176
+ onClick={() => arrayFieldApi.pushValue(field.getItemDefault())}
177
+ >
178
+ Add Item
179
+ </button>
180
+ </div>
181
+ )}
182
+ </form.Field>
183
+ )
184
+ }
185
+ ```
186
+
187
+ ### 8. Working with Variants
188
+
189
+ Use `optionMap` for lookup and `getOptionDefault()` for switching types.
190
+
191
+ ```tsx
192
+ <select
193
+ onChange={(e) => {
194
+ // Switch variant type and default value
195
+ const newValue = field.getOptionDefault(e.target.value)
196
+ form.setFieldValue(field.name, newValue)
197
+ }}
198
+ >
199
+ {field.options.map((opt) => (
200
+ <option key={opt}>{opt}</option>
201
+ ))}
202
+ </select>
203
+ ```
204
+
205
+ ### 9. Type Guards
206
+
207
+ The library exports type guard utilities for safer type narrowing:
208
+
209
+ ```typescript
210
+ import {
211
+ isFieldType,
212
+ isCompoundField,
213
+ isPrimitiveField,
214
+ } from "@ic-reactor/candid"
215
+
216
+ if (isFieldType(field, "record")) {
217
+ // field is RecordArgumentField
218
+ }
219
+ ```
220
+
221
+ ### 10. Recursive Types
222
+
223
+ Recursive types (like linked lists) use `z.lazy()` schemas. Use `field.extract()` to get the inner definition when rendering.
224
+
225
+ ```tsx
226
+ function RecursiveField({ field }) {
227
+ const innerField = useMemo(() => field.extract(), [field])
228
+ return <DynamicField field={innerField} ... />
229
+ }
230
+ ```