@ic-reactor/candid 3.0.14-beta.0 → 3.0.14-beta.2

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 (33) hide show
  1. package/dist/metadata-display-reactor.d.ts +66 -11
  2. package/dist/metadata-display-reactor.d.ts.map +1 -1
  3. package/dist/metadata-display-reactor.js +48 -12
  4. package/dist/metadata-display-reactor.js.map +1 -1
  5. package/dist/visitor/arguments/helpers.d.ts +22 -7
  6. package/dist/visitor/arguments/helpers.d.ts.map +1 -1
  7. package/dist/visitor/arguments/helpers.js +39 -5
  8. package/dist/visitor/arguments/helpers.js.map +1 -1
  9. package/dist/visitor/arguments/index.d.ts.map +1 -1
  10. package/dist/visitor/arguments/index.js +26 -34
  11. package/dist/visitor/arguments/index.js.map +1 -1
  12. package/dist/visitor/arguments/types.d.ts +120 -132
  13. package/dist/visitor/arguments/types.d.ts.map +1 -1
  14. package/dist/visitor/arguments/types.js +25 -1
  15. package/dist/visitor/arguments/types.js.map +1 -1
  16. package/dist/visitor/constants.d.ts.map +1 -1
  17. package/dist/visitor/constants.js +19 -17
  18. package/dist/visitor/constants.js.map +1 -1
  19. package/dist/visitor/returns/index.d.ts.map +1 -1
  20. package/dist/visitor/returns/index.js +24 -13
  21. package/dist/visitor/returns/index.js.map +1 -1
  22. package/dist/visitor/returns/types.d.ts +48 -10
  23. package/dist/visitor/returns/types.d.ts.map +1 -1
  24. package/package.json +2 -2
  25. package/src/metadata-display-reactor.ts +86 -14
  26. package/src/visitor/arguments/helpers.ts +50 -8
  27. package/src/visitor/arguments/index.test.ts +105 -61
  28. package/src/visitor/arguments/index.ts +38 -36
  29. package/src/visitor/arguments/types.ts +211 -220
  30. package/src/visitor/constants.ts +24 -15
  31. package/src/visitor/returns/index.test.ts +30 -30
  32. package/src/visitor/returns/index.ts +52 -21
  33. package/src/visitor/returns/types.ts +60 -17
@@ -58,7 +58,7 @@ describe("ResultFieldVisitor", () => {
58
58
  const ethField = visitor.visitText(IDL.Text, "ethereum_address")
59
59
  expect(ethField.format).toBe("eth")
60
60
 
61
- const accountIdField = visitor.visitText(IDL.Text, "account_id")
61
+ const accountIdField = visitor.visitText(IDL.Text, "account_identifier")
62
62
  expect(accountIdField.format).toBe("account-id")
63
63
 
64
64
  const principalField = visitor.visitText(IDL.Text, "canister_id")
@@ -347,7 +347,7 @@ describe("ResultFieldVisitor", () => {
347
347
  // Validate options by resolving each option
348
348
  const resolvedActive = field.resolve({ Active: null })
349
349
  expect(resolvedActive.selected).toBe("Active")
350
- expect(resolvedActive.selectedOption.type).toBe("null")
350
+ expect(resolvedActive.selectedValue.type).toBe("null")
351
351
  const resolvedInactive = field.resolve({ Inactive: null })
352
352
  expect(resolvedInactive.selected).toBe("Inactive")
353
353
  const resolvedPending = field.resolve({ Pending: null })
@@ -396,14 +396,14 @@ describe("ResultFieldVisitor", () => {
396
396
  Transfer: { from: "aaaaa-aa", to: "bbbbb-bb", amount: BigInt(1) },
397
397
  })
398
398
  expect(transferResolved.selected).toBe("Transfer")
399
- expect(transferResolved.selectedOption.type).toBe("record")
399
+ expect(transferResolved.selectedValue.type).toBe("record")
400
400
  expect(
401
- Object.keys((transferResolved.selectedOption as RecordNode).fields)
401
+ Object.keys((transferResolved.selectedValue as RecordNode).fields)
402
402
  ).toHaveLength(3)
403
403
 
404
404
  const approveResolved = field.resolve({ Approve: BigInt(5) })
405
405
  expect(approveResolved.selected).toBe("Approve")
406
- expect(approveResolved.selectedOption.type).toBe("number")
406
+ expect(approveResolved.selectedValue.type).toBe("number")
407
407
  })
408
408
 
409
409
  it("should detect Result variant (Ok/Err)", () => {
@@ -425,13 +425,13 @@ describe("ResultFieldVisitor", () => {
425
425
 
426
426
  const okResolved = field.resolve({ Ok: BigInt(1) })
427
427
  expect(okResolved.selected).toBe("Ok")
428
- expect(okResolved.selectedOption.type).toBe("number")
429
- expect(okResolved.selectedOption.displayType).toBe("string")
428
+ expect(okResolved.selectedValue.type).toBe("number")
429
+ expect(okResolved.selectedValue.displayType).toBe("string")
430
430
 
431
431
  const errResolved = field.resolve({ Err: "error" })
432
432
  expect(errResolved.selected).toBe("Err")
433
- expect(errResolved.selectedOption.type).toBe("text")
434
- expect(errResolved.selectedOption.displayType).toBe("string")
433
+ expect(errResolved.selectedValue.type).toBe("text")
434
+ expect(errResolved.selectedValue.displayType).toBe("string")
435
435
  })
436
436
 
437
437
  it("should detect complex Result variant", () => {
@@ -474,11 +474,11 @@ describe("ResultFieldVisitor", () => {
474
474
  Ok: { id: BigInt(1), data: new Uint8Array([1, 2, 3]) },
475
475
  })
476
476
  expect(okResolved.selected).toBe("Ok")
477
- expect(okResolved.selectedOption.type).toBe("record")
477
+ expect(okResolved.selectedValue.type).toBe("record")
478
478
 
479
479
  const errResolved = field.resolve({ Err: { NotFound: null } })
480
480
  expect(errResolved.selected).toBe("Err")
481
- expect(errResolved.selectedOption.type).toBe("variant")
481
+ expect(errResolved.selectedValue.type).toBe("variant")
482
482
  })
483
483
 
484
484
  it("should not detect non-Result variant with Ok and other options", () => {
@@ -944,18 +944,18 @@ describe("ResultFieldVisitor", () => {
944
944
  expect(field.displayType).toBe("result")
945
945
 
946
946
  const okResolved = field.resolve({ Ok: BigInt(123) })
947
- if ((okResolved.selectedOption as ResolvedNode).type !== "number") {
947
+ if ((okResolved.selectedValue as ResolvedNode).type !== "number") {
948
948
  throw new Error("Ok field is not number")
949
949
  }
950
- expect((okResolved.selectedOption as ResolvedNode).candidType).toBe("nat")
951
- expect((okResolved.selectedOption as ResolvedNode).displayType).toBe(
950
+ expect((okResolved.selectedValue as ResolvedNode).candidType).toBe("nat")
951
+ expect((okResolved.selectedValue as ResolvedNode).displayType).toBe(
952
952
  "string"
953
953
  )
954
954
 
955
955
  const errResolved = field.resolve({
956
956
  Err: { InsufficientFunds: { balance: BigInt(0) } },
957
957
  })
958
- const innerErr = errResolved.selectedOption as ResolvedNode
958
+ const innerErr = errResolved.selectedValue as ResolvedNode
959
959
  expect(innerErr.type).toBe("variant")
960
960
  const insufficient = (innerErr as any).resolve({
961
961
  InsufficientFunds: { balance: BigInt(0) },
@@ -1269,7 +1269,7 @@ describe("ResultFieldVisitor", () => {
1269
1269
  )
1270
1270
 
1271
1271
  expect(() => field.resolve(null)).toThrow(
1272
- "Expected record for field user, but got null"
1272
+ "Expected record, but got null"
1273
1273
  )
1274
1274
  })
1275
1275
  })
@@ -1292,7 +1292,7 @@ describe("ResultFieldVisitor", () => {
1292
1292
  const resolved = field.resolve({ Ok: "Success" })
1293
1293
  expect(resolved.type).toBe(field.type)
1294
1294
  expect(resolved.selected).toBe("Ok")
1295
- const data = resolved.selectedOption as ResolvedNode
1295
+ const data = resolved.selectedValue as ResolvedNode
1296
1296
  expect(data.value).toBe("Success")
1297
1297
  })
1298
1298
 
@@ -1313,7 +1313,7 @@ describe("ResultFieldVisitor", () => {
1313
1313
  const resolved = field.resolve({ Err: "Something went wrong" })
1314
1314
 
1315
1315
  expect(resolved.selected).toBe("Err")
1316
- const data = resolved.selectedOption as ResolvedNode
1316
+ const data = resolved.selectedValue as ResolvedNode
1317
1317
  expect(data.value).toBe("Something went wrong")
1318
1318
  })
1319
1319
 
@@ -1329,7 +1329,7 @@ describe("ResultFieldVisitor", () => {
1329
1329
  )
1330
1330
 
1331
1331
  expect(() => field.resolve(null)).toThrow(
1332
- "Expected variant for field choice, but got null"
1332
+ "Expected variant, but got null"
1333
1333
  )
1334
1334
  })
1335
1335
  })
@@ -1358,7 +1358,7 @@ describe("ResultFieldVisitor", () => {
1358
1358
  const field = visitor.visitTuple(tupleType, [IDL.Text, IDL.Nat], "pair")
1359
1359
 
1360
1360
  expect(() => field.resolve(null)).toThrow(
1361
- "Expected tuple for field pair, but got null"
1361
+ "Expected tuple, but got null"
1362
1362
  )
1363
1363
  })
1364
1364
  })
@@ -1416,7 +1416,7 @@ describe("ResultFieldVisitor", () => {
1416
1416
  const field = visitor.visitVec(vecType, IDL.Text, "items")
1417
1417
 
1418
1418
  expect(() => field.resolve(null)).toThrow(
1419
- "Expected vector for field items, but got null"
1419
+ "Expected vector, but got null"
1420
1420
  )
1421
1421
  })
1422
1422
  })
@@ -1612,7 +1612,7 @@ describe("ResultFieldVisitor", () => {
1612
1612
 
1613
1613
  const okValue = okResult.results[0] as ResolvedNode
1614
1614
  expect((okValue as any).selected).toBe("Ok")
1615
- expect(((okValue as any).selectedOption as ResolvedNode).value).toBe(
1615
+ expect(((okValue as any).selectedValue as ResolvedNode).value).toBe(
1616
1616
  "12345"
1617
1617
  )
1618
1618
 
@@ -1622,7 +1622,7 @@ describe("ResultFieldVisitor", () => {
1622
1622
  })
1623
1623
  const errValue = errResult.results[0] as ResolvedNode
1624
1624
  expect((errValue as any).selected).toBe("Err")
1625
- expect(((errValue as any).selectedOption as ResolvedNode).value).toBe(
1625
+ expect(((errValue as any).selectedValue as ResolvedNode).value).toBe(
1626
1626
  "Insufficient funds"
1627
1627
  )
1628
1628
  })
@@ -1727,7 +1727,7 @@ describe("ResultFieldVisitor", () => {
1727
1727
 
1728
1728
  const successValue = successResult.results[0] as ResolvedNode
1729
1729
  expect((successValue as any).selected).toBe("Ok")
1730
- expect(((successValue as any).selectedOption as ResolvedNode).value).toBe(
1730
+ expect(((successValue as any).selectedValue as ResolvedNode).value).toBe(
1731
1731
  "1000"
1732
1732
  )
1733
1733
 
@@ -1738,7 +1738,7 @@ describe("ResultFieldVisitor", () => {
1738
1738
  const errorValue = errorResult.results[0] as ResolvedNode
1739
1739
  expect((errorValue as any).selected).toBe("Err")
1740
1740
 
1741
- const val = (errorValue as any).selectedOption as ResolvedNode
1741
+ const val = (errorValue as any).selectedValue as ResolvedNode
1742
1742
  if (typeof val !== "object" || val === null || !("selected" in val)) {
1743
1743
  throw new Error("Expected variant value object")
1744
1744
  }
@@ -1876,7 +1876,7 @@ describe("ResultFieldVisitor", () => {
1876
1876
 
1877
1877
  const variantValue = result.results[0] as ResolvedNode
1878
1878
  expect((variantValue as any).selected).toBe("Ok")
1879
- const innerVal = (variantValue as any).selectedOption as ResolvedNode
1879
+ const innerVal = (variantValue as any).selectedValue as ResolvedNode
1880
1880
  expect(innerVal.value).toBe("12345")
1881
1881
  })
1882
1882
 
@@ -2042,7 +2042,7 @@ describe("ResultFieldVisitor Reproduction - User reported issue", () => {
2042
2042
  const resolved = field.resolve(transformedData)
2043
2043
 
2044
2044
  expect(resolved.selected).toBe("Quorum")
2045
- expect(resolved.selectedOption.value).toBe("some text")
2045
+ expect(resolved.selectedValue.value).toBe("some text")
2046
2046
  })
2047
2047
 
2048
2048
  it("should handle already transformed optional data (unwrapped)", () => {
@@ -2071,7 +2071,7 @@ describe("ResultFieldVisitor Reproduction - User reported issue", () => {
2071
2071
  )
2072
2072
 
2073
2073
  expect(() => field.resolve({ C: null })).toThrow(
2074
- /Option C not found in variant MyVariant. Available options: A, B/
2074
+ /Option "C" not found. Available: A, B/
2075
2075
  )
2076
2076
  })
2077
2077
 
@@ -2101,7 +2101,7 @@ describe("ResultFieldVisitor Reproduction - User reported issue", () => {
2101
2101
  const variantNode = resolved.inner as VariantNode
2102
2102
 
2103
2103
  expect(variantNode.selected).toBe("Quorum")
2104
- expect(variantNode.selectedOption.type).toBe("record")
2104
+ expect(variantNode.selectedValue.type).toBe("record")
2105
2105
  })
2106
2106
 
2107
2107
  it("should handle deeply nested recursive variant with transformed data", () => {
@@ -2130,7 +2130,7 @@ describe("ResultFieldVisitor Reproduction - User reported issue", () => {
2130
2130
  const variantNode = resolved.inner as VariantNode
2131
2131
  expect(variantNode.selected).toBe("Nested")
2132
2132
 
2133
- const nestedResolved = variantNode.selectedOption as RecursiveNode
2133
+ const nestedResolved = variantNode.selectedValue as RecursiveNode
2134
2134
  const innerVariant = nestedResolved.inner as VariantNode
2135
2135
  expect(innerVariant.selected).toBe("Quorum")
2136
2136
  })
@@ -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,10 +59,17 @@ 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"
62
+ type NodeTypeExtras<T extends VisitorDataType> = T extends "record"
59
63
  ? { fields: Record<string, ResultNode> }
60
64
  : T extends "variant"
61
- ? { selectedOption: ResultNode; selected?: string }
65
+ ? {
66
+ /** All variant options as schema */
67
+ options: Record<string, ResultNode>
68
+ /** The resolved selected option value */
69
+ selectedValue: ResultNode
70
+ /** The selected option key (populated after resolution) */
71
+ selected?: string
72
+ }
62
73
  : T extends "tuple" | "vector"
63
74
  ? { items: ResultNode[] }
64
75
  : T extends "optional"
@@ -86,18 +97,20 @@ type NodeTypeExtras<T extends NodeType> = T extends "record"
86
97
  * resolved children directly in their structure fields.
87
98
  * Primitive types store the display value in `value`.
88
99
  */
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
- }
100
+ export type ResultNode<T extends VisitorDataType = VisitorDataType> =
101
+ ResultNodeBase<T> &
102
+ NodeTypeExtras<T> & {
103
+ /** Resolve this node with a value, returning a new resolved node */
104
+ resolve(data: unknown): ResolvedNode<T>
105
+ }
94
106
 
95
107
  /**
96
108
  * A resolved node has `raw` populated and children resolved.
97
109
  */
98
- export type ResolvedNode<T extends NodeType = NodeType> = ResultNode<T> & {
99
- raw: unknown
100
- }
110
+ export type ResolvedNode<T extends VisitorDataType = VisitorDataType> =
111
+ ResultNode<T> & {
112
+ raw: unknown
113
+ }
101
114
 
102
115
  // ════════════════════════════════════════════════════════════════════════════
103
116
  // Convenience Type Aliases
@@ -121,27 +134,57 @@ export type UnknownNode = ResultNode<"unknown">
121
134
  // Method & Service Level
122
135
  // ════════════════════════════════════════════════════════════════════════════
123
136
 
137
+ /**
138
+ * Metadata for a single method's return values.
139
+ * Use this to render method results.
140
+ */
124
141
  export interface MethodMeta<
125
142
  A = BaseActor,
126
143
  Name extends FunctionName<A> = FunctionName<A>,
127
144
  > {
145
+ /** Whether this is a "query" or "update" call */
128
146
  functionType: FunctionType
147
+ /** The method name as defined in the Candid interface */
129
148
  functionName: Name
149
+ /** Array of result node descriptors, one per return value */
130
150
  returns: ResultNode[]
151
+ /** Number of return values */
131
152
  returnCount: number
132
153
  /**
133
154
  * Resolve the method result schema with actual return data.
155
+ * @param data The raw return data from the canister
156
+ * @returns A resolved result with display-friendly values
134
157
  */
135
- resolve(data: ActorMethodReturnType<A[Name]>): ResolvedMethodResult<A>
158
+ resolve(data: ActorMethodReturnType<A[Name]>): MethodResult<A>
136
159
  }
137
160
 
138
- export interface ResolvedMethodResult<A = BaseActor> {
161
+ /**
162
+ * A resolved method result with display-friendly values.
163
+ */
164
+ export interface MethodResult<A = BaseActor> {
165
+ /** Whether this is a "query" or "update" call */
139
166
  functionType: FunctionType
167
+ /** The method name */
140
168
  functionName: FunctionName<A>
169
+ /** Resolved return values */
141
170
  results: ResolvedNode[]
171
+ /** Original raw data from the canister */
142
172
  raw: ActorMethodReturnType<A[FunctionName<A>]>
143
173
  }
144
174
 
175
+ /**
176
+ * Service-level metadata mapping method names to their return metadata.
177
+ */
145
178
  export type ServiceMeta<A = BaseActor> = {
146
179
  [K in FunctionName<A>]: MethodMeta<A, K>
147
180
  }
181
+
182
+ /**
183
+ * Props type for result display components.
184
+ */
185
+ export type ResultDisplayProps<T extends VisitorDataType = VisitorDataType> = {
186
+ /** The resolved result node */
187
+ node: ResolvedNode<T>
188
+ /** Nesting depth for indentation */
189
+ depth?: number
190
+ }