@ic-reactor/candid 3.0.14-beta.1 → 3.0.14-beta.3

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.
@@ -1,12 +1,14 @@
1
1
  import { isQuery } from "../helpers"
2
2
  import { checkTextFormat, checkNumberFormat } from "../constants"
3
+ import { formatLabel } from "../arguments/helpers"
4
+ import { MetadataError } from "../arguments/types"
3
5
  import type {
4
6
  ResultNode,
5
7
  ResolvedNode,
6
- NodeType,
8
+ VisitorDataType,
7
9
  MethodMeta,
8
10
  ServiceMeta,
9
- ResolvedMethodResult,
11
+ MethodResult,
10
12
  NumberFormat,
11
13
  TextFormat,
12
14
  } from "./types"
@@ -32,7 +34,7 @@ type Codec = { decode: (v: unknown) => unknown }
32
34
  /**
33
35
  * Creates a primitive node with automatic resolve implementation.
34
36
  */
35
- function primitiveNode<T extends NodeType>(
37
+ function primitiveNode<T extends VisitorDataType>(
36
38
  type: T,
37
39
  label: string,
38
40
  candidType: string,
@@ -43,6 +45,7 @@ function primitiveNode<T extends NodeType>(
43
45
  const node: ResultNode<T> = {
44
46
  type,
45
47
  label,
48
+ displayLabel: formatLabel(label),
46
49
  candidType,
47
50
  displayType,
48
51
  ...extras,
@@ -54,8 +57,10 @@ function primitiveNode<T extends NodeType>(
54
57
  raw: data,
55
58
  } as unknown as ResolvedNode<T>
56
59
  } catch (e) {
57
- throw new Error(
58
- `Failed to decode field "${label}" of type ${type} (${candidType}) with data: ${JSON.stringify(data)}. Error: ${e}`
60
+ throw new MetadataError(
61
+ `Failed to decode: ${e instanceof Error ? e.message : String(e)}`,
62
+ label,
63
+ candidType
59
64
  )
60
65
  }
61
66
  },
@@ -116,7 +121,7 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
116
121
  returnCount: t.retTypes.length,
117
122
  resolve: (
118
123
  data: ActorMethodReturnType<A[FunctionName<A>]>
119
- ): ResolvedMethodResult<A> => {
124
+ ): MethodResult<A> => {
120
125
  const dataArray = returns.length <= 1 ? [data] : (data as unknown[])
121
126
  return {
122
127
  functionType,
@@ -145,12 +150,17 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
145
150
  const node: ResultNode<"record"> = {
146
151
  type: "record",
147
152
  label,
153
+ displayLabel: formatLabel(label),
148
154
  candidType: "record",
149
155
  displayType: "object",
150
156
  fields,
151
157
  resolve(data: unknown): ResolvedNode<"record"> {
152
158
  if (data === null || data === undefined) {
153
- throw new Error(`Expected record for field ${label}, but got ${data}`)
159
+ throw new MetadataError(
160
+ `Expected record, but got ${data === null ? "null" : "undefined"}`,
161
+ label,
162
+ "record"
163
+ )
154
164
  }
155
165
  const recordData = data as Record<string, unknown>
156
166
  const resolvedFields: Record<string, ResolvedNode> = {}
@@ -161,8 +171,10 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
161
171
  recordData[key] !== undefined ? recordData[key] : recordData[index]
162
172
 
163
173
  if (!field || typeof field.resolve !== "function") {
164
- throw new Error(
165
- `Field "${key}" in record "${label}" is not a valid ResultNode. Got: ${JSON.stringify(field)}`
174
+ throw new MetadataError(
175
+ `Field "${key}" is not a valid ResultNode`,
176
+ `${label}.${key}`,
177
+ "record"
166
178
  )
167
179
  }
168
180
 
@@ -180,38 +192,44 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
180
192
  fields_: Array<[string, IDL.Type]>,
181
193
  label: string
182
194
  ): ResultNode<"variant"> {
183
- const selectedOption: Record<string, ResultNode> = {}
195
+ const options: Record<string, ResultNode> = {}
184
196
  for (const [key, type] of fields_) {
185
- selectedOption[key] = type.accept(this, key) as ResultNode
197
+ options[key] = type.accept(this, key) as ResultNode
186
198
  }
187
- const isResult = "Ok" in selectedOption && "Err" in selectedOption
199
+ const isResult = "Ok" in options && "Err" in options
188
200
  const node: ResultNode<"variant"> = {
189
201
  type: "variant",
190
202
  label,
203
+ displayLabel: formatLabel(label),
191
204
  candidType: "variant",
192
205
  displayType: isResult ? "result" : "variant",
193
- selectedOption: {} as ResultNode, // placeholder, populated on resolve
206
+ options,
207
+ selectedValue: {} as ResultNode, // placeholder, populated on resolve
194
208
  resolve(data: unknown): ResolvedNode<"variant"> {
195
209
  if (data === null || data === undefined) {
196
- throw new Error(
197
- `Expected variant for field ${label}, but got ${data}`
210
+ throw new MetadataError(
211
+ `Expected variant, but got ${data === null ? "null" : "undefined"}`,
212
+ label,
213
+ "variant"
198
214
  )
199
215
  }
200
216
  const variantData = data as Record<string, unknown>
201
217
  // Support both raw { Selected: value } and transformed { _type: 'Selected', Selected: value }
202
218
  const selected =
203
219
  (variantData._type as string) || Object.keys(variantData)[0]
204
- const optionNode = selectedOption[selected]
220
+ const optionNode = options[selected]
205
221
 
206
222
  if (!optionNode) {
207
- throw new Error(
208
- `Option ${selected} not found in variant ${label}. Available options: ${Object.keys(selectedOption).join(", ")}`
223
+ throw new MetadataError(
224
+ `Option "${selected}" not found. Available: ${Object.keys(options).join(", ")}`,
225
+ label,
226
+ "variant"
209
227
  )
210
228
  }
211
229
  return {
212
230
  ...node,
213
231
  selected,
214
- selectedOption: optionNode.resolve(variantData[selected]),
232
+ selectedValue: optionNode.resolve(variantData[selected]),
215
233
  raw: data,
216
234
  }
217
235
  },
@@ -231,12 +249,17 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
231
249
  const node: ResultNode<"tuple"> = {
232
250
  type: "tuple",
233
251
  label,
252
+ displayLabel: formatLabel(label),
234
253
  candidType: "tuple",
235
254
  displayType: "array",
236
255
  items,
237
256
  resolve(data: unknown): ResolvedNode<"tuple"> {
238
257
  if (data === null || data === undefined) {
239
- throw new Error(`Expected tuple for field ${label}, but got ${data}`)
258
+ throw new MetadataError(
259
+ `Expected tuple, but got ${data === null ? "null" : "undefined"}`,
260
+ label,
261
+ "tuple"
262
+ )
240
263
  }
241
264
  const tupleData = data as unknown[]
242
265
  return {
@@ -259,6 +282,7 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
259
282
  const node: ResultNode<"optional"> = {
260
283
  type: "optional",
261
284
  label,
285
+ displayLabel: formatLabel(label),
262
286
  candidType: "opt",
263
287
  displayType: "nullable",
264
288
  value: null, // null until resolved
@@ -290,6 +314,7 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
290
314
  const node: ResultNode<"blob"> = {
291
315
  type: "blob",
292
316
  label,
317
+ displayLabel: formatLabel(label),
293
318
  candidType: "blob",
294
319
  displayType: "string",
295
320
  length: 0,
@@ -321,12 +346,17 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
321
346
  const node: ResultNode<"vector"> = {
322
347
  type: "vector",
323
348
  label,
349
+ displayLabel: formatLabel(label),
324
350
  candidType: "vec",
325
351
  displayType: "array",
326
352
  items: [], // empty schema placeholder, populated on resolve
327
353
  resolve(data: unknown): ResolvedNode<"vector"> {
328
354
  if (data === null || data === undefined) {
329
- throw new Error(`Expected vector for field ${label}, but got ${data}`)
355
+ throw new MetadataError(
356
+ `Expected vector, but got ${data === null ? "null" : "undefined"}`,
357
+ label,
358
+ "vec"
359
+ )
330
360
  }
331
361
  const vectorData = data as unknown[]
332
362
  return {
@@ -357,6 +387,7 @@ export class ResultFieldVisitor<A = BaseActor> extends IDL.Visitor<
357
387
  const node: ResultNode<"recursive"> = {
358
388
  type: "recursive",
359
389
  label,
390
+ displayLabel: formatLabel(label),
360
391
  candidType: "rec",
361
392
  displayType: "recursive",
362
393
  inner: {} as ResultNode, // placeholder, populated on resolve
@@ -6,14 +6,16 @@ import type {
6
6
  } from "@ic-reactor/core"
7
7
  import type { VisitorDataType, TextFormat, NumberFormat } from "../types"
8
8
 
9
- export type { TextFormat, NumberFormat }
9
+ export type { VisitorDataType, TextFormat, NumberFormat }
10
10
 
11
11
  // ════════════════════════════════════════════════════════════════════════════
12
12
  // Core Types & Formats
13
13
  // ════════════════════════════════════════════════════════════════════════════
14
14
 
15
- export type NodeType = VisitorDataType
16
-
15
+ /**
16
+ * The display type category after transformation.
17
+ * Maps Candid types to JavaScript-friendly display types.
18
+ */
17
19
  export type DisplayType =
18
20
  | "string"
19
21
  | "number"
@@ -35,11 +37,13 @@ export type DisplayType =
35
37
  /**
36
38
  * Base properties shared by all result nodes.
37
39
  */
38
- interface ResultNodeBase<T extends NodeType = NodeType> {
40
+ interface ResultNodeBase<T extends VisitorDataType = VisitorDataType> {
39
41
  /** The Candid type category */
40
42
  type: T
41
- /** Human-readable label */
43
+ /** Raw label from Candid definition */
42
44
  label: string
45
+ /** Human-readable formatted label for display */
46
+ displayLabel: string
43
47
  /** Original Candid type name */
44
48
  candidType: string
45
49
  /** What it becomes after display transformation */
@@ -55,27 +59,112 @@ interface ResultNodeBase<T extends NodeType = NodeType> {
55
59
  // For compound types, children are stored directly in their respective fields
56
60
  // ════════════════════════════════════════════════════════════════════════════
57
61
 
58
- type NodeTypeExtras<T extends NodeType> = T extends "record"
59
- ? { fields: Record<string, ResultNode> }
62
+ interface RecordNodeExtras {
63
+ /** Child fields of the record */
64
+ fields: Record<string, ResultNode>
65
+ }
66
+
67
+ interface VariantNodeExtras {
68
+ /** All variant options as schema */
69
+ options: Record<string, ResultNode>
70
+ /** The resolved selected option value */
71
+ selectedValue: ResultNode
72
+ /** The selected option key (populated after resolution) */
73
+ selected?: string
74
+ }
75
+
76
+ interface TupleNodeExtras {
77
+ /** Tuple element fields */
78
+ items: ResultNode[]
79
+ }
80
+
81
+ interface VectorNodeExtras {
82
+ /** Vector element fields */
83
+ items: ResultNode[]
84
+ }
85
+
86
+ interface OptionalNodeExtras {
87
+ /** The inner value, or null if not enabled */
88
+ value: ResultNode | null
89
+ }
90
+
91
+ interface RecursiveNodeExtras {
92
+ /** The resolved recursive inner type */
93
+ inner: ResultNode
94
+ }
95
+
96
+ interface BlobNodeExtras {
97
+ /** The blob value as hex/base64 or Uint8Array */
98
+ value: string | Uint8Array
99
+ /** Hash of the blob content */
100
+ hash: string
101
+ /** Length in bytes */
102
+ length: number
103
+ }
104
+
105
+ interface NumberNodeExtras {
106
+ /** Detected number format */
107
+ format: NumberFormat
108
+ /** The numeric value */
109
+ value: string | number
110
+ }
111
+
112
+ interface TextNodeExtras {
113
+ /** Detected text format */
114
+ format: TextFormat
115
+ /** The text value */
116
+ value: string
117
+ }
118
+
119
+ interface PrincipalNodeExtras {
120
+ /** Detected text format */
121
+ format: TextFormat
122
+ /** The principal value as string */
123
+ value: string
124
+ }
125
+
126
+ interface BooleanNodeExtras {
127
+ /** The boolean value */
128
+ value: boolean
129
+ }
130
+
131
+ interface NullNodeExtras {
132
+ /** The null value */
133
+ value: null
134
+ }
135
+
136
+ interface UnknownNodeExtras {
137
+ /** The unknown value */
138
+ value: unknown
139
+ }
140
+
141
+ type NodeTypeExtras<T extends VisitorDataType> = T extends "record"
142
+ ? RecordNodeExtras
60
143
  : T extends "variant"
61
- ? { selectedOption: ResultNode; selected?: string }
62
- : T extends "tuple" | "vector"
63
- ? { items: ResultNode[] }
64
- : T extends "optional"
65
- ? { value: ResultNode | null }
66
- : T extends "recursive"
67
- ? { inner: ResultNode }
68
- : T extends "blob"
69
- ? { value: string | Uint8Array; hash: string; length: number }
70
- : T extends "number"
71
- ? { format: NumberFormat; value: string | number }
72
- : T extends "text" | "principal"
73
- ? { format: TextFormat; value: string }
74
- : T extends "boolean"
75
- ? { value: boolean }
76
- : T extends "null"
77
- ? { value: null }
78
- : { value: unknown } // unknown
144
+ ? VariantNodeExtras
145
+ : T extends "tuple"
146
+ ? TupleNodeExtras
147
+ : T extends "vector"
148
+ ? VectorNodeExtras
149
+ : T extends "optional"
150
+ ? OptionalNodeExtras
151
+ : T extends "recursive"
152
+ ? RecursiveNodeExtras
153
+ : T extends "blob"
154
+ ? BlobNodeExtras
155
+ : T extends "number"
156
+ ? NumberNodeExtras
157
+ : T extends "text"
158
+ ? TextNodeExtras
159
+ : T extends "principal"
160
+ ? PrincipalNodeExtras
161
+ : T extends "boolean"
162
+ ? BooleanNodeExtras
163
+ : T extends "null"
164
+ ? NullNodeExtras
165
+ : T extends "unknown"
166
+ ? UnknownNodeExtras
167
+ : {}
79
168
 
80
169
  /**
81
170
  * A unified result node that contains both schema and resolved value.
@@ -86,18 +175,20 @@ type NodeTypeExtras<T extends NodeType> = T extends "record"
86
175
  * resolved children directly in their structure fields.
87
176
  * Primitive types store the display value in `value`.
88
177
  */
89
- export type ResultNode<T extends NodeType = NodeType> = ResultNodeBase<T> &
90
- NodeTypeExtras<T> & {
91
- /** Resolve this node with a value, returning a new resolved node */
92
- resolve(data: unknown): ResolvedNode<T>
93
- }
178
+ export type ResultNode<T extends VisitorDataType = VisitorDataType> =
179
+ ResultNodeBase<T> &
180
+ NodeTypeExtras<T> & {
181
+ /** Resolve this node with a value, returning a new resolved node */
182
+ resolve(data: unknown): ResolvedNode<T>
183
+ }
94
184
 
95
185
  /**
96
186
  * A resolved node has `raw` populated and children resolved.
97
187
  */
98
- export type ResolvedNode<T extends NodeType = NodeType> = ResultNode<T> & {
99
- raw: unknown
100
- }
188
+ export type ResolvedNode<T extends VisitorDataType = VisitorDataType> =
189
+ ResultNode<T> & {
190
+ raw: unknown
191
+ }
101
192
 
102
193
  // ════════════════════════════════════════════════════════════════════════════
103
194
  // Convenience Type Aliases
@@ -121,27 +212,57 @@ export type UnknownNode = ResultNode<"unknown">
121
212
  // Method & Service Level
122
213
  // ════════════════════════════════════════════════════════════════════════════
123
214
 
215
+ /**
216
+ * Metadata for a single method's return values.
217
+ * Use this to render method results.
218
+ */
124
219
  export interface MethodMeta<
125
220
  A = BaseActor,
126
221
  Name extends FunctionName<A> = FunctionName<A>,
127
222
  > {
223
+ /** Whether this is a "query" or "update" call */
128
224
  functionType: FunctionType
225
+ /** The method name as defined in the Candid interface */
129
226
  functionName: Name
227
+ /** Array of result node descriptors, one per return value */
130
228
  returns: ResultNode[]
229
+ /** Number of return values */
131
230
  returnCount: number
132
231
  /**
133
232
  * Resolve the method result schema with actual return data.
233
+ * @param data The raw return data from the canister
234
+ * @returns A resolved result with display-friendly values
134
235
  */
135
- resolve(data: ActorMethodReturnType<A[Name]>): ResolvedMethodResult<A>
236
+ resolve(data: ActorMethodReturnType<A[Name]>): MethodResult<A>
136
237
  }
137
238
 
138
- export interface ResolvedMethodResult<A = BaseActor> {
239
+ /**
240
+ * A resolved method result with display-friendly values.
241
+ */
242
+ export interface MethodResult<A = BaseActor> {
243
+ /** Whether this is a "query" or "update" call */
139
244
  functionType: FunctionType
245
+ /** The method name */
140
246
  functionName: FunctionName<A>
247
+ /** Resolved return values */
141
248
  results: ResolvedNode[]
249
+ /** Original raw data from the canister */
142
250
  raw: ActorMethodReturnType<A[FunctionName<A>]>
143
251
  }
144
252
 
253
+ /**
254
+ * Service-level metadata mapping method names to their return metadata.
255
+ */
145
256
  export type ServiceMeta<A = BaseActor> = {
146
257
  [K in FunctionName<A>]: MethodMeta<A, K>
147
258
  }
259
+
260
+ /**
261
+ * Props type for result display components.
262
+ */
263
+ export type ResultDisplayProps<T extends VisitorDataType = VisitorDataType> = {
264
+ /** The resolved result node */
265
+ node: ResolvedNode<T>
266
+ /** Nesting depth for indentation */
267
+ depth?: number
268
+ }