@ic-reactor/candid 3.0.9-beta.0 → 3.0.10-beta.1

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.
@@ -2,25 +2,17 @@ import { isQuery } from "../helpers"
2
2
  import { checkTextFormat, checkNumberFormat } from "../constants"
3
3
  import { IDL } from "../types"
4
4
  import type {
5
- ResultField,
6
- ResultFieldWithValue,
7
- RecordResultField,
8
- VariantResultField,
9
- TupleResultField,
10
- OptionalResultField,
11
- VectorResultField,
12
- BlobResultField,
13
- RecursiveResultField,
14
- PrincipalResultField,
15
- NumberResultField,
16
- TextResultField,
17
- BooleanResultField,
18
- NullResultField,
19
- UnknownResultField,
20
- MethodResultMeta,
21
- ServiceResultMeta,
5
+ ResultNode,
6
+ ResolvedNode,
7
+ NodeType,
8
+ MethodMeta,
9
+ ServiceMeta,
22
10
  ResolvedMethodResult,
11
+ NumberFormat,
12
+ TextFormat,
23
13
  } from "./types"
14
+
15
+ export * from "./types"
24
16
  import { DisplayCodecVisitor } from "@ic-reactor/core"
25
17
  import type {
26
18
  ActorMethodReturnType,
@@ -31,527 +23,385 @@ import type {
31
23
 
32
24
  export * from "./types"
33
25
 
26
+ // ════════════════════════════════════════════════════════════════════════════
27
+ // Node Factory - Eliminates Boilerplate
28
+ // ════════════════════════════════════════════════════════════════════════════
29
+
30
+ type Codec = { decode: (v: unknown) => unknown }
31
+
34
32
  /**
35
- * ResultFieldVisitor generates metadata for displaying method results from Candid IDL types.
36
- *
37
- * ## Design Principles
38
- *
39
- * 1. **Works with raw IDL types BEFORE transformation** - generates metadata at initialization
40
- * 2. **No value dependencies** - metadata describes structure, not specific values
41
- * 3. **Describes display format** - includes hints for how values will appear after transformation
42
- * 4. **Efficient** - single traversal, reusable metadata
43
- * 5. **Resolvable** - metadata can be combined with values at runtime via .resolve(val)
44
- *
45
- * ## Key Insight: Metadata vs Values
46
- *
47
- * The visitor generates a "schema" that describes:
48
- * - What type each field is in Candid (nat, Principal, opt, etc.)
49
- * - What it becomes after display transformation (string, null, etc.)
50
- * - How it should be formatted (timestamp, cycle, hex, etc.)
51
- *
52
- * Values are NOT passed during traversal. Instead, the generated schema
53
- * is used at render time to properly display transformed values.
54
- *
55
- * ## Display Transformations (applied by DisplayCodecVisitor)
56
- *
57
- * | Candid Type | Display Type | Notes |
58
- * |-------------|--------------|-------|
59
- * | nat, int, nat64, int64 | string | BigInt → string |
60
- * | Principal | string | Principal.toText() |
61
- * | opt T | T \| null | [value] → value, [] → null |
62
- * | vec nat8 (blob) | string | Uint8Array → hex string |
63
- * | variant { Ok, Err } | unwrapped | { Ok: val } → val (or throws on Err) |
64
- *
65
- * @example
66
- * ```typescript
67
- * const visitor = new ResultFieldVisitor()
68
- * const serviceMeta = service.accept(visitor, null)
69
- *
70
- * // Get method result metadata
71
- * const methodMeta = serviceMeta["icrc1_balance_of"]
72
- * // methodMeta.resultFields[0] = {
73
- * // type: "number",
74
- * // candidType: "nat",
75
- * // displayType: "string",
76
- * // numberFormat: "normal"
77
- * // }
78
- *
79
- * // At render time, apply to transformed value:
80
- * const transformedResult = "1000000000" // Already transformed by DisplayCodec
81
- * renderField(methodMeta.resultFields[0], transformedResult)
82
- * ```
33
+ * Creates a primitive node with automatic resolve implementation.
83
34
  */
35
+ function primitiveNode<T extends NodeType>(
36
+ type: T,
37
+ label: string,
38
+ candidType: string,
39
+ displayType: ResultNode["displayType"],
40
+ codec: Codec,
41
+ extras: object = {}
42
+ ): ResultNode<T> {
43
+ const node: ResultNode<T> = {
44
+ type,
45
+ label,
46
+ candidType,
47
+ displayType,
48
+ ...extras,
49
+ resolve(data: unknown): ResolvedNode<T> {
50
+ return {
51
+ ...node,
52
+ value: codec.decode(data),
53
+ raw: data,
54
+ } as unknown as ResolvedNode<T>
55
+ },
56
+ } as unknown as ResultNode<T>
57
+ return node
58
+ }
59
+
60
+ // ════════════════════════════════════════════════════════════════════════════
61
+ // Simplified Result Field Visitor
62
+ // ════════════════════════════════════════════════════════════════════════════
63
+
84
64
  export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
85
65
  string,
86
- ResultField | MethodResultMeta<A> | ServiceResultMeta<A>
66
+ ResultNode | MethodMeta<A> | ServiceMeta<A>
87
67
  > {
88
- private displayVisitor = new DisplayCodecVisitor()
68
+ private codec = new DisplayCodecVisitor()
89
69
 
90
- // ════════════════════════════════════════════════════════════════════════
91
- // Service & Function Level
92
- // ════════════════════════════════════════════════════════════════════════
70
+ private getCodec(t: IDL.Type): Codec {
71
+ return t.accept(this.codec, null) as Codec
72
+ }
93
73
 
94
- public visitService(t: IDL.ServiceClass): ServiceResultMeta<A> {
95
- const result = {} as ServiceResultMeta<A>
74
+ // ══════════════════════════════════════════════════════════════════════════
75
+ // Service & Function
76
+ // ══════════════════════════════════════════════════════════════════════════
96
77
 
97
- for (const [functionName, func] of t._fields) {
98
- result[functionName as FunctionName<A>] = func.accept(
99
- this,
100
- functionName
101
- ) as MethodResultMeta<A>
78
+ public visitService(t: IDL.ServiceClass): ServiceMeta<A> {
79
+ const result = {} as ServiceMeta<A>
80
+ for (const [name, func] of t._fields) {
81
+ result[name as FunctionName<A>] = func.accept(this, name) as MethodMeta<A>
102
82
  }
103
-
104
83
  return result
105
84
  }
106
85
 
107
86
  public visitFunc(
108
87
  t: IDL.FuncClass,
109
88
  functionName: FunctionName<A>
110
- ): MethodResultMeta<A> {
89
+ ): MethodMeta<A> {
111
90
  const functionType: FunctionType = isQuery(t) ? "query" : "update"
112
-
113
- const resultFields = t.retTypes.map((retType, index) =>
114
- retType.accept(this, `__ret${index}`)
115
- ) as ResultField[]
116
-
117
- const generateMetadata = (
118
- data: ActorMethodReturnType<A[FunctionName<A>]>
119
- ): ResolvedMethodResult<A> => {
120
- const dataArray: unknown[] =
121
- resultFields.length === 0
122
- ? []
123
- : resultFields.length === 1
124
- ? [data]
125
- : (data as unknown[])
126
-
127
- const results = resultFields.map((field, index) =>
128
- field.resolve(dataArray[index])
129
- )
130
-
131
- return {
132
- functionType,
133
- functionName,
134
- results,
135
- raw: data,
136
- }
137
- }
91
+ const returns = t.retTypes.map((ret, i) =>
92
+ ret.accept(this, `__ret${i}`)
93
+ ) as ResultNode[]
138
94
 
139
95
  return {
140
96
  functionType,
141
97
  functionName,
142
- resultFields,
98
+ returns,
143
99
  returnCount: t.retTypes.length,
144
- generateMetadata,
100
+ resolve: (
101
+ data: ActorMethodReturnType<A[FunctionName<A>]>
102
+ ): ResolvedMethodResult<A> => {
103
+ const dataArray = returns.length <= 1 ? [data] : (data as unknown[])
104
+ return {
105
+ functionType,
106
+ functionName,
107
+ results: returns.map((node, i) => node.resolve(dataArray[i])),
108
+ raw: data,
109
+ }
110
+ },
145
111
  }
146
112
  }
147
113
 
148
- // ════════════════════════════════════════════════════════════════════════
114
+ // ══════════════════════════════════════════════════════════════════════════
149
115
  // Compound Types
150
- // ════════════════════════════════════════════════════════════════════════
116
+ // ══════════════════════════════════════════════════════════════════════════
151
117
 
152
118
  public visitRecord(
153
119
  _t: IDL.RecordClass,
154
120
  fields_: Array<[string, IDL.Type]>,
155
121
  label: string
156
- ): RecordResultField {
157
- const fields = fields_.map(
158
- ([key, type]) => type.accept(this, key) as ResultField
159
- )
122
+ ): ResultNode<"record"> {
123
+ const fields: Record<string, ResultNode> = {}
124
+ for (const [key, type] of fields_) {
125
+ fields[key] = type.accept(this, key) as ResultNode
126
+ }
160
127
 
161
- const field: RecordResultField = {
128
+ const node: ResultNode<"record"> = {
162
129
  type: "record",
163
130
  label,
164
131
  candidType: "record",
165
132
  displayType: "object",
166
133
  fields,
167
- resolve(value: unknown): ResultFieldWithValue<"record"> {
168
- const record = value as Record<string, unknown> | null | undefined
169
- if (!record) {
170
- throw new Error(
171
- `Expected record for field ${label}, but got ${value}`
172
- )
134
+ resolve(data: unknown): ResolvedNode<"record"> {
135
+ if (data === null || data === undefined) {
136
+ throw new Error(`Expected record for field ${label}, but got ${data}`)
173
137
  }
174
-
175
- const resolvedFields: Record<string, ResultFieldWithValue> = {}
176
- for (const f of fields) {
177
- resolvedFields[f.label] = f.resolve(record[f.label])
138
+ const recordData = data as Record<string, unknown>
139
+ const resolvedFields: Record<string, ResolvedNode> = {}
140
+ for (const [key, field] of Object.entries(fields)) {
141
+ resolvedFields[key] = field.resolve(recordData[key])
178
142
  }
179
-
180
- return { field, value: resolvedFields, raw: value }
143
+ return { ...node, fields: resolvedFields, raw: data }
181
144
  },
182
145
  }
183
- return field
146
+ return node
184
147
  }
185
148
 
186
149
  public visitVariant(
187
150
  _t: IDL.VariantClass,
188
151
  fields_: Array<[string, IDL.Type]>,
189
152
  label: string
190
- ): VariantResultField {
191
- const options: string[] = []
192
- const optionFields: ResultField[] = []
193
-
153
+ ): ResultNode<"variant"> {
154
+ const selectedOption: Record<string, ResultNode> = {}
194
155
  for (const [key, type] of fields_) {
195
- options.push(key)
196
- optionFields.push(type.accept(this, key) as ResultField)
156
+ selectedOption[key] = type.accept(this, key) as ResultNode
197
157
  }
198
-
199
- // Detect if this is a Result type (has Ok and Err options)
200
- const isResult = options.includes("Ok") && options.includes("Err")
201
- const displayType = isResult ? "result" : "variant"
202
-
203
- const field: VariantResultField = {
158
+ const isResult = "Ok" in selectedOption && "Err" in selectedOption
159
+ const node: ResultNode<"variant"> = {
204
160
  type: "variant",
205
161
  label,
206
162
  candidType: "variant",
207
- displayType,
208
- options,
209
- optionFields,
210
- resolve(value: unknown): ResultFieldWithValue<"variant"> {
211
- if (value == null) {
212
- throw new Error(`Expected variant for field ${label}, but got null`)
213
- }
214
-
215
- const variant = value as Record<string, unknown>
216
- const optionsInValue = Object.keys(variant)
217
- const activeOption = optionsInValue.find((opt) => options.includes(opt))
218
-
219
- if (!activeOption) {
163
+ displayType: isResult ? "result" : "variant",
164
+ selectedOption: {} as ResultNode, // placeholder, populated on resolve
165
+ resolve(data: unknown): ResolvedNode<"variant"> {
166
+ if (data === null || data === undefined) {
220
167
  throw new Error(
221
- `Expected variant option for field ${label}, but got none`
168
+ `Expected variant for field ${label}, but got ${data}`
222
169
  )
223
170
  }
224
-
225
- const activeValue = variant[activeOption]
226
- const optionIndex = options.indexOf(activeOption)
227
- const optionField = optionFields[optionIndex]
228
-
229
- const specificField: VariantResultField = {
230
- ...field,
231
- options,
232
- optionFields: [optionField],
171
+ const variantData = data as Record<string, unknown>
172
+ const selected = Object.keys(variantData)[0]
173
+ const optionNode = selectedOption[selected]
174
+ if (!optionNode) {
175
+ throw new Error(`Option ${selected} not found in variant`)
233
176
  }
234
-
235
177
  return {
236
- field: specificField,
237
- value: {
238
- option: activeOption,
239
- value: optionField?.resolve(activeValue) ?? {
240
- field: optionField,
241
- value: activeValue,
242
- raw: activeValue,
243
- },
244
- },
245
- raw: value,
178
+ ...node,
179
+ selected,
180
+ selectedOption: optionNode.resolve(variantData[selected]),
181
+ raw: data,
246
182
  }
247
183
  },
248
184
  }
249
-
250
- return field
185
+ return node
251
186
  }
252
187
 
253
188
  public visitTuple<T extends IDL.Type[]>(
254
189
  _t: IDL.TupleClass<T>,
255
190
  components: IDL.Type[],
256
191
  label: string
257
- ): TupleResultField {
258
- const fields = components.map(
259
- (type, index) => type.accept(this, `_${index}`) as ResultField
192
+ ): ResultNode<"tuple"> {
193
+ const items = components.map(
194
+ (t, i) => t.accept(this, `_${i}`) as ResultNode
260
195
  )
261
196
 
262
- const field: TupleResultField = {
197
+ const node: ResultNode<"tuple"> = {
263
198
  type: "tuple",
264
199
  label,
265
200
  candidType: "tuple",
266
201
  displayType: "array",
267
- fields,
268
- resolve(value: unknown): ResultFieldWithValue<"tuple"> {
269
- const tuple = value as unknown[] | null | undefined
270
- if (tuple == null) {
271
- throw new Error(`Expected tuple for field ${label}, but got null`)
202
+ items,
203
+ resolve(data: unknown): ResolvedNode<"tuple"> {
204
+ if (data === null || data === undefined) {
205
+ throw new Error(`Expected tuple for field ${label}, but got ${data}`)
206
+ }
207
+ const tupleData = data as unknown[]
208
+ return {
209
+ ...node,
210
+ items: items.map((item, i) => item.resolve(tupleData[i])),
211
+ raw: data,
272
212
  }
273
-
274
- const resolvedItems = fields.map((f, index) => f.resolve(tuple[index]))
275
- return { field, value: resolvedItems, raw: value }
276
213
  },
277
214
  }
278
-
279
- return field
215
+ return node
280
216
  }
281
217
 
282
218
  public visitOpt<T>(
283
219
  _t: IDL.OptClass<T>,
284
220
  ty: IDL.Type<T>,
285
221
  label: string
286
- ): OptionalResultField {
287
- const innerField = ty.accept(this, label) as ResultField
222
+ ): ResultNode<"optional"> {
223
+ const inner = ty.accept(this, label) as ResultNode
288
224
 
289
- const field: OptionalResultField = {
225
+ const node: ResultNode<"optional"> = {
290
226
  type: "optional",
291
227
  label,
292
228
  candidType: "opt",
293
229
  displayType: "nullable",
294
- innerField,
295
- resolve(value: unknown): ResultFieldWithValue<"optional"> {
296
- const opt = value as [unknown] | [] | null | undefined
297
- if (opt == null || opt.length === 0) {
298
- return { field, value: null, raw: value }
299
- }
300
- return { field, value: innerField.resolve(opt[0]), raw: value }
230
+ value: null, // null until resolved
231
+ resolve(data: unknown): ResolvedNode<"optional"> {
232
+ const opt = data as T[]
233
+ const resolved =
234
+ Array.isArray(opt) && opt.length > 0 ? inner.resolve(opt[0]) : null
235
+ return { ...node, value: resolved, raw: data }
301
236
  },
302
237
  }
303
-
304
- return field
238
+ return node
305
239
  }
306
240
 
307
241
  public visitVec<T>(
308
242
  _t: IDL.VecClass<T>,
309
243
  ty: IDL.Type<T>,
310
244
  label: string
311
- ): VectorResultField | BlobResultField {
312
- // Check if it's blob (vec nat8)
245
+ ): ResultNode<"vector"> | ResultNode<"blob"> {
246
+ // Blob detection (vec nat8)
313
247
  if (ty instanceof IDL.FixedNatClass && ty._bits === 8) {
314
- const codec = _t.accept(this.displayVisitor, null) as any
315
- const blobField: BlobResultField = {
248
+ const codec = this.getCodec(_t)
249
+ const node: ResultNode<"blob"> = {
316
250
  type: "blob",
317
251
  label,
318
252
  candidType: "blob",
319
- displayType: "string", // Transformed to hex string
320
- displayHint: "hex",
321
- resolve(value: unknown): ResultFieldWithValue<"blob"> {
322
- return {
323
- field: blobField,
324
- value: codec.decode(value),
325
- raw: value,
326
- }
253
+ displayType: "string",
254
+ value: "", // empty schema placeholder, populated on resolve
255
+ resolve(data: unknown): ResolvedNode<"blob"> {
256
+ return { ...node, value: codec.decode(data) as string, raw: data }
327
257
  },
328
258
  }
329
- return blobField
259
+ return node
330
260
  }
331
261
 
332
- const itemField = ty.accept(this, "item") as ResultField
262
+ const itemSchema = ty.accept(this, "item") as ResultNode
333
263
 
334
- const field: VectorResultField = {
264
+ const node: ResultNode<"vector"> = {
335
265
  type: "vector",
336
266
  label,
337
267
  candidType: "vec",
338
268
  displayType: "array",
339
- itemField,
340
- resolve(value: unknown): ResultFieldWithValue<"vector"> {
341
- const vec = value as unknown[] | null | undefined
342
- if (vec == null) {
343
- throw new Error(`Expected vector for field ${label}, but got null`)
269
+ items: [], // empty schema placeholder, populated on resolve
270
+ resolve(data: unknown): ResolvedNode<"vector"> {
271
+ if (data === null || data === undefined) {
272
+ throw new Error(`Expected vector for field ${label}, but got ${data}`)
273
+ }
274
+ const vectorData = data as unknown[]
275
+ return {
276
+ ...node,
277
+ items: vectorData.map((v) => itemSchema.resolve(v)),
278
+ raw: data,
344
279
  }
345
-
346
- const resolvedItems = vec.map((item) => itemField.resolve(item))
347
- return { field, value: resolvedItems, raw: value }
348
280
  },
349
281
  }
350
-
351
- return field
282
+ return node
352
283
  }
353
284
 
354
285
  public visitRec<T>(
355
- t: IDL.RecClass<T>,
286
+ _t: IDL.RecClass<T>,
356
287
  ty: IDL.ConstructType<T>,
357
288
  label: string
358
- ): RecursiveResultField {
359
- // eslint-disable-next-line @typescript-eslint/no-this-alias
289
+ ): ResultNode<"recursive"> {
360
290
  const self = this
291
+ // Lazy extraction to prevent infinite loops
292
+ let innerSchema: ResultNode | null = null
293
+ const getInner = () =>
294
+ (innerSchema ??= ty.accept(self, label) as ResultNode)
361
295
 
362
- const field: RecursiveResultField = {
296
+ const node: ResultNode<"recursive"> = {
363
297
  type: "recursive",
364
298
  label,
365
299
  candidType: "rec",
366
300
  displayType: "recursive",
367
- typeName: t.name,
368
- // Lazy extraction to prevent infinite loops
369
- extract: () => ty.accept(self, label) as ResultField,
370
- resolve(value: unknown): ResultFieldWithValue {
371
- // Extract the inner field and resolve with it
372
- const innerField = field.extract()
373
- return innerField.resolve(value) // Resolve to union type
301
+ inner: {} as ResultNode, // placeholder, populated on resolve
302
+ resolve(data: unknown): ResolvedNode<"recursive"> {
303
+ return { ...node, inner: getInner().resolve(data), raw: data }
374
304
  },
375
305
  }
376
-
377
- return field
306
+ return node
378
307
  }
379
308
 
380
- // ════════════════════════════════════════════════════════════════════════
381
- // Primitive Types
382
- // ════════════════════════════════════════════════════════════════════════
309
+ // ══════════════════════════════════════════════════════════════════════════
310
+ // Primitives - Using Factory
311
+ // ══════════════════════════════════════════════════════════════════════════
383
312
 
384
313
  public visitPrincipal(
385
314
  t: IDL.PrincipalClass,
386
315
  label: string
387
- ): PrincipalResultField {
388
- const codec = t.accept(this.displayVisitor, null) as any
389
- const field: PrincipalResultField = {
390
- type: "principal",
316
+ ): ResultNode<"principal"> {
317
+ return primitiveNode(
318
+ "principal",
391
319
  label,
392
- candidType: "principal",
393
- displayType: "string", // Principal.toText()
394
- textFormat: checkTextFormat(label),
395
- resolve(value: unknown): ResultFieldWithValue<"principal"> {
396
- return {
397
- field,
398
- value: codec.decode(value),
399
- raw: value,
400
- }
401
- },
402
- }
403
-
404
- return field
320
+ "principal",
321
+ "string",
322
+ this.getCodec(t),
323
+ {
324
+ format: checkTextFormat(label) as TextFormat,
325
+ }
326
+ )
405
327
  }
406
328
 
407
- public visitText(t: IDL.TextClass, label: string): TextResultField {
408
- const codec = t.accept(this.displayVisitor, null) as any
409
- const field: TextResultField = {
410
- type: "text",
411
- label,
412
- candidType: "text",
413
- displayType: "string",
414
- textFormat: checkTextFormat(label),
415
- resolve(value: unknown): ResultFieldWithValue<"text"> {
416
- return { field, value: codec.decode(value), raw: value }
417
- },
418
- }
419
-
420
- return field
329
+ public visitText(t: IDL.TextClass, label: string): ResultNode<"text"> {
330
+ return primitiveNode("text", label, "text", "string", this.getCodec(t), {
331
+ format: checkTextFormat(label) as TextFormat,
332
+ })
421
333
  }
422
334
 
423
- public visitBool(t: IDL.BoolClass, label: string): BooleanResultField {
424
- const codec = t.accept(this.displayVisitor, null) as any
425
- const field: BooleanResultField = {
426
- type: "boolean",
427
- label,
428
- candidType: "bool",
429
- displayType: "boolean",
430
- resolve(value: unknown): ResultFieldWithValue<"boolean"> {
431
- return { field, value: codec.decode(value), raw: value }
432
- },
433
- }
434
-
435
- return field
335
+ public visitBool(t: IDL.BoolClass, label: string): ResultNode<"boolean"> {
336
+ return primitiveNode("boolean", label, "bool", "boolean", this.getCodec(t))
436
337
  }
437
338
 
438
- public visitNull(t: IDL.NullClass, label: string): NullResultField {
439
- const codec = t.accept(this.displayVisitor, null) as any
440
- const field: NullResultField = {
441
- type: "null",
442
- label,
443
- candidType: "null",
444
- displayType: "null",
445
- resolve(value: unknown): ResultFieldWithValue<"null"> {
446
- return { field, value: codec.decode(value), raw: value }
447
- },
448
- }
449
-
450
- return field
339
+ public visitNull(t: IDL.NullClass, label: string): ResultNode<"null"> {
340
+ return primitiveNode("null", label, "null", "null", this.getCodec(t))
451
341
  }
452
342
 
453
- // Numbers
454
- public visitInt(t: IDL.IntClass, label: string): NumberResultField {
455
- const codec = t.accept(this.displayVisitor, null) as any
456
- const field: NumberResultField = {
457
- type: "number",
458
- label,
459
- candidType: "int",
460
- displayType: "string", // BigInt → string
461
- numberFormat: checkNumberFormat(label),
462
- resolve(value: unknown): ResultFieldWithValue<"number"> {
463
- return { field, value: codec.decode(value), raw: value }
464
- },
465
- }
466
-
467
- return field
343
+ public visitInt(t: IDL.IntClass, label: string): ResultNode<"number"> {
344
+ return primitiveNode("number", label, "int", "string", this.getCodec(t), {
345
+ format: checkNumberFormat(label) as NumberFormat,
346
+ })
468
347
  }
469
348
 
470
- public visitNat(t: IDL.NatClass, label: string): NumberResultField {
471
- const codec = t.accept(this.displayVisitor, null) as any
472
- const field: NumberResultField = {
473
- type: "number",
474
- label,
475
- candidType: "nat",
476
- displayType: "string", // BigInt → string
477
- numberFormat: checkNumberFormat(label),
478
- resolve(value: unknown): ResultFieldWithValue<"number"> {
479
- return { field, value: codec.decode(value), raw: value }
480
- },
481
- }
482
-
483
- return field
349
+ public visitNat(t: IDL.NatClass, label: string): ResultNode<"number"> {
350
+ return primitiveNode("number", label, "nat", "string", this.getCodec(t), {
351
+ format: checkNumberFormat(label) as NumberFormat,
352
+ })
484
353
  }
485
354
 
486
- public visitFloat(t: IDL.FloatClass, label: string): NumberResultField {
487
- const codec = t.accept(this.displayVisitor, null) as any
488
- const field: NumberResultField = {
489
- type: "number",
355
+ public visitFloat(t: IDL.FloatClass, label: string): ResultNode<"number"> {
356
+ return primitiveNode(
357
+ "number",
490
358
  label,
491
- candidType: `float${t._bits}`,
492
- displayType: "number", // Floats stay as numbers
493
- numberFormat: checkNumberFormat(label),
494
- resolve(value: unknown): ResultFieldWithValue<"number"> {
495
- return { field, value: codec.decode(value), raw: value }
496
- },
497
- }
498
-
499
- return field
359
+ `float${t._bits}`,
360
+ "number",
361
+ this.getCodec(t),
362
+ {
363
+ format: checkNumberFormat(label) as NumberFormat,
364
+ }
365
+ )
500
366
  }
501
367
 
502
- public visitFixedInt(t: IDL.FixedIntClass, label: string): NumberResultField {
368
+ public visitFixedInt(
369
+ t: IDL.FixedIntClass,
370
+ label: string
371
+ ): ResultNode<"number"> {
503
372
  const bits = t._bits
504
- const codec = t.accept(this.displayVisitor, null) as any
505
- const field: NumberResultField = {
506
- type: "number",
373
+ return primitiveNode(
374
+ "number",
507
375
  label,
508
- candidType: `int${bits}`,
509
- displayType: bits <= 32 ? "number" : "string", // int64 → string
510
- numberFormat: checkNumberFormat(label),
511
- resolve(value: unknown): ResultFieldWithValue<"number"> {
512
- return {
513
- field,
514
- value: codec.decode(value),
515
- raw: value,
516
- }
517
- },
518
- }
519
-
520
- return field
376
+ `int${bits}`,
377
+ bits <= 32 ? "number" : "string",
378
+ this.getCodec(t),
379
+ {
380
+ format: checkNumberFormat(label) as NumberFormat,
381
+ }
382
+ )
521
383
  }
522
384
 
523
- public visitFixedNat(t: IDL.FixedNatClass, label: string): NumberResultField {
385
+ public visitFixedNat(
386
+ t: IDL.FixedNatClass,
387
+ label: string
388
+ ): ResultNode<"number"> {
524
389
  const bits = t._bits
525
- const codec = t.accept(this.displayVisitor, null) as any
526
- const field: NumberResultField = {
527
- type: "number",
390
+ return primitiveNode(
391
+ "number",
528
392
  label,
529
- candidType: `nat${bits}`,
530
- displayType: bits <= 32 ? "number" : "string", // nat64 → string
531
- numberFormat: checkNumberFormat(label),
532
- resolve(value: unknown): ResultFieldWithValue<"number"> {
533
- return {
534
- field,
535
- value: codec.decode(value),
536
- raw: value,
537
- }
538
- },
539
- }
540
-
541
- return field
393
+ `nat${bits}`,
394
+ bits <= 32 ? "number" : "string",
395
+ this.getCodec(t),
396
+ {
397
+ format: checkNumberFormat(label) as NumberFormat,
398
+ }
399
+ )
542
400
  }
543
401
 
544
- public visitType<T>(_t: IDL.Type<T>, label: string): UnknownResultField {
545
- const field: UnknownResultField = {
546
- type: "unknown",
547
- label,
548
- candidType: "unknown",
549
- displayType: "unknown",
550
- resolve(value: unknown): ResultFieldWithValue<"unknown"> {
551
- return { field, value, raw: value }
552
- },
553
- }
554
-
555
- return field
402
+ public visitType<T>(_t: IDL.Type<T>, label: string): ResultNode<"unknown"> {
403
+ return primitiveNode("unknown", label, "unknown", "unknown", {
404
+ decode: (v) => v,
405
+ })
556
406
  }
557
407
  }