@ic-reactor/candid 3.0.8-beta.2 → 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 +77 -120
- 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 +323 -390
- package/src/visitor/returns/index.ts +239 -385
- package/src/visitor/returns/types.ts +96 -200
|
@@ -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,523 +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
|
-
return { field, value: null, raw: value }
|
|
134
|
+
resolve(data: unknown): ResolvedNode<"record"> {
|
|
135
|
+
if (data === null || data === undefined) {
|
|
136
|
+
throw new Error(`Expected record for field ${label}, but got ${data}`)
|
|
171
137
|
}
|
|
172
|
-
|
|
173
|
-
const resolvedFields: Record<string,
|
|
174
|
-
for (const
|
|
175
|
-
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])
|
|
176
142
|
}
|
|
177
|
-
|
|
178
|
-
return { field, value: resolvedFields, raw: value }
|
|
143
|
+
return { ...node, fields: resolvedFields, raw: data }
|
|
179
144
|
},
|
|
180
145
|
}
|
|
181
|
-
return
|
|
146
|
+
return node
|
|
182
147
|
}
|
|
183
148
|
|
|
184
149
|
public visitVariant(
|
|
185
150
|
_t: IDL.VariantClass,
|
|
186
151
|
fields_: Array<[string, IDL.Type]>,
|
|
187
152
|
label: string
|
|
188
|
-
):
|
|
189
|
-
const
|
|
190
|
-
const optionFields: ResultField[] = []
|
|
191
|
-
|
|
153
|
+
): ResultNode<"variant"> {
|
|
154
|
+
const selectedOption: Record<string, ResultNode> = {}
|
|
192
155
|
for (const [key, type] of fields_) {
|
|
193
|
-
|
|
194
|
-
optionFields.push(type.accept(this, key) as ResultField)
|
|
156
|
+
selectedOption[key] = type.accept(this, key) as ResultNode
|
|
195
157
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const isResult = options.includes("Ok") && options.includes("Err")
|
|
199
|
-
const displayType = isResult ? "result" : "variant"
|
|
200
|
-
|
|
201
|
-
const field: VariantResultField = {
|
|
158
|
+
const isResult = "Ok" in selectedOption && "Err" in selectedOption
|
|
159
|
+
const node: ResultNode<"variant"> = {
|
|
202
160
|
type: "variant",
|
|
203
161
|
label,
|
|
204
162
|
candidType: "variant",
|
|
205
|
-
displayType,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Expected variant for field ${label}, but got ${data}`
|
|
169
|
+
)
|
|
211
170
|
}
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!activeOption) {
|
|
218
|
-
return { field, value: null, raw: value }
|
|
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`)
|
|
219
176
|
}
|
|
220
|
-
|
|
221
|
-
const activeValue = variant[activeOption]
|
|
222
|
-
const optionIndex = options.indexOf(activeOption)
|
|
223
|
-
const optionField = optionFields[optionIndex]
|
|
224
|
-
|
|
225
|
-
const specificField: VariantResultField = {
|
|
226
|
-
...field,
|
|
227
|
-
options,
|
|
228
|
-
optionFields: [optionField],
|
|
229
|
-
}
|
|
230
|
-
|
|
231
177
|
return {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
field: optionField,
|
|
237
|
-
value: activeValue,
|
|
238
|
-
raw: activeValue,
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
raw: value,
|
|
178
|
+
...node,
|
|
179
|
+
selected,
|
|
180
|
+
selectedOption: optionNode.resolve(variantData[selected]),
|
|
181
|
+
raw: data,
|
|
242
182
|
}
|
|
243
183
|
},
|
|
244
184
|
}
|
|
245
|
-
|
|
246
|
-
return field
|
|
185
|
+
return node
|
|
247
186
|
}
|
|
248
187
|
|
|
249
188
|
public visitTuple<T extends IDL.Type[]>(
|
|
250
189
|
_t: IDL.TupleClass<T>,
|
|
251
190
|
components: IDL.Type[],
|
|
252
191
|
label: string
|
|
253
|
-
):
|
|
254
|
-
const
|
|
255
|
-
(
|
|
192
|
+
): ResultNode<"tuple"> {
|
|
193
|
+
const items = components.map(
|
|
194
|
+
(t, i) => t.accept(this, `_${i}`) as ResultNode
|
|
256
195
|
)
|
|
257
196
|
|
|
258
|
-
const
|
|
197
|
+
const node: ResultNode<"tuple"> = {
|
|
259
198
|
type: "tuple",
|
|
260
199
|
label,
|
|
261
200
|
candidType: "tuple",
|
|
262
201
|
displayType: "array",
|
|
263
|
-
|
|
264
|
-
resolve(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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,
|
|
268
212
|
}
|
|
269
|
-
|
|
270
|
-
const resolvedItems = fields.map((f, index) => f.resolve(tuple[index]))
|
|
271
|
-
return { field, value: resolvedItems, raw: value }
|
|
272
213
|
},
|
|
273
214
|
}
|
|
274
|
-
|
|
275
|
-
return field
|
|
215
|
+
return node
|
|
276
216
|
}
|
|
277
217
|
|
|
278
218
|
public visitOpt<T>(
|
|
279
219
|
_t: IDL.OptClass<T>,
|
|
280
220
|
ty: IDL.Type<T>,
|
|
281
221
|
label: string
|
|
282
|
-
):
|
|
283
|
-
const
|
|
222
|
+
): ResultNode<"optional"> {
|
|
223
|
+
const inner = ty.accept(this, label) as ResultNode
|
|
284
224
|
|
|
285
|
-
const
|
|
225
|
+
const node: ResultNode<"optional"> = {
|
|
286
226
|
type: "optional",
|
|
287
227
|
label,
|
|
288
228
|
candidType: "opt",
|
|
289
229
|
displayType: "nullable",
|
|
290
|
-
|
|
291
|
-
resolve(
|
|
292
|
-
const opt =
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
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 }
|
|
297
236
|
},
|
|
298
237
|
}
|
|
299
|
-
|
|
300
|
-
return field
|
|
238
|
+
return node
|
|
301
239
|
}
|
|
302
240
|
|
|
303
241
|
public visitVec<T>(
|
|
304
242
|
_t: IDL.VecClass<T>,
|
|
305
243
|
ty: IDL.Type<T>,
|
|
306
244
|
label: string
|
|
307
|
-
):
|
|
308
|
-
//
|
|
245
|
+
): ResultNode<"vector"> | ResultNode<"blob"> {
|
|
246
|
+
// Blob detection (vec nat8)
|
|
309
247
|
if (ty instanceof IDL.FixedNatClass && ty._bits === 8) {
|
|
310
|
-
const codec =
|
|
311
|
-
const
|
|
248
|
+
const codec = this.getCodec(_t)
|
|
249
|
+
const node: ResultNode<"blob"> = {
|
|
312
250
|
type: "blob",
|
|
313
251
|
label,
|
|
314
252
|
candidType: "blob",
|
|
315
|
-
displayType: "string",
|
|
316
|
-
|
|
317
|
-
resolve(
|
|
318
|
-
return {
|
|
319
|
-
field: blobField,
|
|
320
|
-
value: codec.decode(value),
|
|
321
|
-
raw: value,
|
|
322
|
-
}
|
|
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 }
|
|
323
257
|
},
|
|
324
258
|
}
|
|
325
|
-
return
|
|
259
|
+
return node
|
|
326
260
|
}
|
|
327
261
|
|
|
328
|
-
const
|
|
262
|
+
const itemSchema = ty.accept(this, "item") as ResultNode
|
|
329
263
|
|
|
330
|
-
const
|
|
264
|
+
const node: ResultNode<"vector"> = {
|
|
331
265
|
type: "vector",
|
|
332
266
|
label,
|
|
333
267
|
candidType: "vec",
|
|
334
268
|
displayType: "array",
|
|
335
|
-
|
|
336
|
-
resolve(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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,
|
|
340
279
|
}
|
|
341
|
-
|
|
342
|
-
const resolvedItems = vec.map((item) => itemField.resolve(item))
|
|
343
|
-
return { field, value: resolvedItems, raw: value }
|
|
344
280
|
},
|
|
345
281
|
}
|
|
346
|
-
|
|
347
|
-
return field
|
|
282
|
+
return node
|
|
348
283
|
}
|
|
349
284
|
|
|
350
285
|
public visitRec<T>(
|
|
351
|
-
|
|
286
|
+
_t: IDL.RecClass<T>,
|
|
352
287
|
ty: IDL.ConstructType<T>,
|
|
353
288
|
label: string
|
|
354
|
-
):
|
|
355
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
289
|
+
): ResultNode<"recursive"> {
|
|
356
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)
|
|
357
295
|
|
|
358
|
-
const
|
|
296
|
+
const node: ResultNode<"recursive"> = {
|
|
359
297
|
type: "recursive",
|
|
360
298
|
label,
|
|
361
299
|
candidType: "rec",
|
|
362
300
|
displayType: "recursive",
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
367
|
-
// Extract the inner field and resolve with it
|
|
368
|
-
const innerField = field.extract()
|
|
369
|
-
return innerField.resolve(value)
|
|
301
|
+
inner: {} as ResultNode, // placeholder, populated on resolve
|
|
302
|
+
resolve(data: unknown): ResolvedNode<"recursive"> {
|
|
303
|
+
return { ...node, inner: getInner().resolve(data), raw: data }
|
|
370
304
|
},
|
|
371
305
|
}
|
|
372
|
-
|
|
373
|
-
return field
|
|
306
|
+
return node
|
|
374
307
|
}
|
|
375
308
|
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
309
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
// Primitives - Using Factory
|
|
311
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
379
312
|
|
|
380
313
|
public visitPrincipal(
|
|
381
314
|
t: IDL.PrincipalClass,
|
|
382
315
|
label: string
|
|
383
|
-
):
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
type: "principal",
|
|
316
|
+
): ResultNode<"principal"> {
|
|
317
|
+
return primitiveNode(
|
|
318
|
+
"principal",
|
|
387
319
|
label,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
raw: value,
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return field
|
|
320
|
+
"principal",
|
|
321
|
+
"string",
|
|
322
|
+
this.getCodec(t),
|
|
323
|
+
{
|
|
324
|
+
format: checkTextFormat(label) as TextFormat,
|
|
325
|
+
}
|
|
326
|
+
)
|
|
401
327
|
}
|
|
402
328
|
|
|
403
|
-
public visitText(t: IDL.TextClass, label: string):
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
label,
|
|
408
|
-
candidType: "text",
|
|
409
|
-
displayType: "string",
|
|
410
|
-
textFormat: checkTextFormat(label),
|
|
411
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
412
|
-
return { field, value: codec.decode(value), raw: value }
|
|
413
|
-
},
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
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
|
+
})
|
|
417
333
|
}
|
|
418
334
|
|
|
419
|
-
public visitBool(t: IDL.BoolClass, label: string):
|
|
420
|
-
|
|
421
|
-
const field: BooleanResultField = {
|
|
422
|
-
type: "boolean",
|
|
423
|
-
label,
|
|
424
|
-
candidType: "bool",
|
|
425
|
-
displayType: "boolean",
|
|
426
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
427
|
-
return { field, value: codec.decode(value), raw: value }
|
|
428
|
-
},
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return field
|
|
335
|
+
public visitBool(t: IDL.BoolClass, label: string): ResultNode<"boolean"> {
|
|
336
|
+
return primitiveNode("boolean", label, "bool", "boolean", this.getCodec(t))
|
|
432
337
|
}
|
|
433
338
|
|
|
434
|
-
public visitNull(t: IDL.NullClass, label: string):
|
|
435
|
-
|
|
436
|
-
const field: NullResultField = {
|
|
437
|
-
type: "null",
|
|
438
|
-
label,
|
|
439
|
-
candidType: "null",
|
|
440
|
-
displayType: "null",
|
|
441
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
442
|
-
return { field, value: codec.decode(value), raw: value }
|
|
443
|
-
},
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return field
|
|
339
|
+
public visitNull(t: IDL.NullClass, label: string): ResultNode<"null"> {
|
|
340
|
+
return primitiveNode("null", label, "null", "null", this.getCodec(t))
|
|
447
341
|
}
|
|
448
342
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
type: "number",
|
|
454
|
-
label,
|
|
455
|
-
candidType: "int",
|
|
456
|
-
displayType: "string", // BigInt → string
|
|
457
|
-
numberFormat: checkNumberFormat(label),
|
|
458
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
459
|
-
return { field, value: codec.decode(value), raw: value }
|
|
460
|
-
},
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
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
|
+
})
|
|
464
347
|
}
|
|
465
348
|
|
|
466
|
-
public visitNat(t: IDL.NatClass, label: string):
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
label,
|
|
471
|
-
candidType: "nat",
|
|
472
|
-
displayType: "string", // BigInt → string
|
|
473
|
-
numberFormat: checkNumberFormat(label),
|
|
474
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
475
|
-
return { field, value: codec.decode(value), raw: value }
|
|
476
|
-
},
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
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
|
+
})
|
|
480
353
|
}
|
|
481
354
|
|
|
482
|
-
public visitFloat(t: IDL.FloatClass, label: string):
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
type: "number",
|
|
355
|
+
public visitFloat(t: IDL.FloatClass, label: string): ResultNode<"number"> {
|
|
356
|
+
return primitiveNode(
|
|
357
|
+
"number",
|
|
486
358
|
label,
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
return field
|
|
359
|
+
`float${t._bits}`,
|
|
360
|
+
"number",
|
|
361
|
+
this.getCodec(t),
|
|
362
|
+
{
|
|
363
|
+
format: checkNumberFormat(label) as NumberFormat,
|
|
364
|
+
}
|
|
365
|
+
)
|
|
496
366
|
}
|
|
497
367
|
|
|
498
|
-
public visitFixedInt(
|
|
368
|
+
public visitFixedInt(
|
|
369
|
+
t: IDL.FixedIntClass,
|
|
370
|
+
label: string
|
|
371
|
+
): ResultNode<"number"> {
|
|
499
372
|
const bits = t._bits
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
type: "number",
|
|
373
|
+
return primitiveNode(
|
|
374
|
+
"number",
|
|
503
375
|
label,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
raw: value,
|
|
512
|
-
}
|
|
513
|
-
},
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return field
|
|
376
|
+
`int${bits}`,
|
|
377
|
+
bits <= 32 ? "number" : "string",
|
|
378
|
+
this.getCodec(t),
|
|
379
|
+
{
|
|
380
|
+
format: checkNumberFormat(label) as NumberFormat,
|
|
381
|
+
}
|
|
382
|
+
)
|
|
517
383
|
}
|
|
518
384
|
|
|
519
|
-
public visitFixedNat(
|
|
385
|
+
public visitFixedNat(
|
|
386
|
+
t: IDL.FixedNatClass,
|
|
387
|
+
label: string
|
|
388
|
+
): ResultNode<"number"> {
|
|
520
389
|
const bits = t._bits
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
type: "number",
|
|
390
|
+
return primitiveNode(
|
|
391
|
+
"number",
|
|
524
392
|
label,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
raw: value,
|
|
533
|
-
}
|
|
534
|
-
},
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return field
|
|
393
|
+
`nat${bits}`,
|
|
394
|
+
bits <= 32 ? "number" : "string",
|
|
395
|
+
this.getCodec(t),
|
|
396
|
+
{
|
|
397
|
+
format: checkNumberFormat(label) as NumberFormat,
|
|
398
|
+
}
|
|
399
|
+
)
|
|
538
400
|
}
|
|
539
401
|
|
|
540
|
-
public visitType<T>(_t: IDL.Type<T>, label: string):
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
candidType: "unknown",
|
|
545
|
-
displayType: "unknown",
|
|
546
|
-
resolve(value: unknown): ResultFieldWithValue {
|
|
547
|
-
return { field, value, raw: value }
|
|
548
|
-
},
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
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
|
+
})
|
|
552
406
|
}
|
|
553
407
|
}
|