@ic-reactor/candid 3.0.9-beta.0 → 3.0.10-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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata-display-reactor.d.ts +4 -4
- package/dist/metadata-display-reactor.d.ts.map +1 -1
- package/dist/metadata-display-reactor.js +1 -1
- package/dist/metadata-display-reactor.js.map +1 -1
- package/dist/visitor/returns/index.d.ts +23 -71
- package/dist/visitor/returns/index.d.ts.map +1 -1
- package/dist/visitor/returns/index.js +156 -306
- package/dist/visitor/returns/index.js.map +1 -1
- package/dist/visitor/returns/types.d.ts +68 -192
- package/dist/visitor/returns/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/metadata-display-reactor.ts +8 -8
- package/src/visitor/returns/index.test.ts +311 -382
- package/src/visitor/returns/index.ts +237 -387
- package/src/visitor/returns/types.ts +97 -298
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
66
|
+
ResultNode | MethodMeta<A> | ServiceMeta<A>
|
|
87
67
|
> {
|
|
88
|
-
private
|
|
68
|
+
private codec = new DisplayCodecVisitor()
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
70
|
+
private getCodec(t: IDL.Type): Codec {
|
|
71
|
+
return t.accept(this.codec, null) as Codec
|
|
72
|
+
}
|
|
93
73
|
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
75
|
+
// Service & Function
|
|
76
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
):
|
|
89
|
+
): MethodMeta<A> {
|
|
111
90
|
const functionType: FunctionType = isQuery(t) ? "query" : "update"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
98
|
+
returns,
|
|
143
99
|
returnCount: t.retTypes.length,
|
|
144
|
-
|
|
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
|
-
):
|
|
157
|
-
const fields =
|
|
158
|
-
|
|
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
|
|
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(
|
|
168
|
-
|
|
169
|
-
|
|
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,
|
|
176
|
-
for (const
|
|
177
|
-
resolvedFields[
|
|
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
|
|
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
|
-
):
|
|
191
|
-
const
|
|
192
|
-
const optionFields: ResultField[] = []
|
|
193
|
-
|
|
153
|
+
): ResultNode<"variant"> {
|
|
154
|
+
const selectedOption: Record<string, ResultNode> = {}
|
|
194
155
|
for (const [key, type] of fields_) {
|
|
195
|
-
|
|
196
|
-
optionFields.push(type.accept(this, key) as ResultField)
|
|
156
|
+
selectedOption[key] = type.accept(this, key) as ResultNode
|
|
197
157
|
}
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
168
|
+
`Expected variant for field ${label}, but got ${data}`
|
|
222
169
|
)
|
|
223
170
|
}
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
):
|
|
258
|
-
const
|
|
259
|
-
(
|
|
192
|
+
): ResultNode<"tuple"> {
|
|
193
|
+
const items = components.map(
|
|
194
|
+
(t, i) => t.accept(this, `_${i}`) as ResultNode
|
|
260
195
|
)
|
|
261
196
|
|
|
262
|
-
const
|
|
197
|
+
const node: ResultNode<"tuple"> = {
|
|
263
198
|
type: "tuple",
|
|
264
199
|
label,
|
|
265
200
|
candidType: "tuple",
|
|
266
201
|
displayType: "array",
|
|
267
|
-
|
|
268
|
-
resolve(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
):
|
|
287
|
-
const
|
|
222
|
+
): ResultNode<"optional"> {
|
|
223
|
+
const inner = ty.accept(this, label) as ResultNode
|
|
288
224
|
|
|
289
|
-
const
|
|
225
|
+
const node: ResultNode<"optional"> = {
|
|
290
226
|
type: "optional",
|
|
291
227
|
label,
|
|
292
228
|
candidType: "opt",
|
|
293
229
|
displayType: "nullable",
|
|
294
|
-
|
|
295
|
-
resolve(
|
|
296
|
-
const opt =
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
):
|
|
312
|
-
//
|
|
245
|
+
): ResultNode<"vector"> | ResultNode<"blob"> {
|
|
246
|
+
// Blob detection (vec nat8)
|
|
313
247
|
if (ty instanceof IDL.FixedNatClass && ty._bits === 8) {
|
|
314
|
-
const codec =
|
|
315
|
-
const
|
|
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",
|
|
320
|
-
|
|
321
|
-
resolve(
|
|
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
|
|
259
|
+
return node
|
|
330
260
|
}
|
|
331
261
|
|
|
332
|
-
const
|
|
262
|
+
const itemSchema = ty.accept(this, "item") as ResultNode
|
|
333
263
|
|
|
334
|
-
const
|
|
264
|
+
const node: ResultNode<"vector"> = {
|
|
335
265
|
type: "vector",
|
|
336
266
|
label,
|
|
337
267
|
candidType: "vec",
|
|
338
268
|
displayType: "array",
|
|
339
|
-
|
|
340
|
-
resolve(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
286
|
+
_t: IDL.RecClass<T>,
|
|
356
287
|
ty: IDL.ConstructType<T>,
|
|
357
288
|
label: string
|
|
358
|
-
):
|
|
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
|
|
296
|
+
const node: ResultNode<"recursive"> = {
|
|
363
297
|
type: "recursive",
|
|
364
298
|
label,
|
|
365
299
|
candidType: "rec",
|
|
366
300
|
displayType: "recursive",
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
//
|
|
382
|
-
//
|
|
309
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// Primitives - Using Factory
|
|
311
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
383
312
|
|
|
384
313
|
public visitPrincipal(
|
|
385
314
|
t: IDL.PrincipalClass,
|
|
386
315
|
label: string
|
|
387
|
-
):
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
type: "principal",
|
|
316
|
+
): ResultNode<"principal"> {
|
|
317
|
+
return primitiveNode(
|
|
318
|
+
"principal",
|
|
391
319
|
label,
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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):
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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):
|
|
424
|
-
|
|
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):
|
|
439
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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):
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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):
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
type: "number",
|
|
355
|
+
public visitFloat(t: IDL.FloatClass, label: string): ResultNode<"number"> {
|
|
356
|
+
return primitiveNode(
|
|
357
|
+
"number",
|
|
490
358
|
label,
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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(
|
|
368
|
+
public visitFixedInt(
|
|
369
|
+
t: IDL.FixedIntClass,
|
|
370
|
+
label: string
|
|
371
|
+
): ResultNode<"number"> {
|
|
503
372
|
const bits = t._bits
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
type: "number",
|
|
373
|
+
return primitiveNode(
|
|
374
|
+
"number",
|
|
507
375
|
label,
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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(
|
|
385
|
+
public visitFixedNat(
|
|
386
|
+
t: IDL.FixedNatClass,
|
|
387
|
+
label: string
|
|
388
|
+
): ResultNode<"number"> {
|
|
524
389
|
const bits = t._bits
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
type: "number",
|
|
390
|
+
return primitiveNode(
|
|
391
|
+
"number",
|
|
528
392
|
label,
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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):
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
}
|