@ic-reactor/candid 3.0.7-beta.1 → 3.0.8-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/README.md +5 -1
- package/dist/display-reactor.d.ts +3 -2
- package/dist/display-reactor.d.ts.map +1 -1
- package/dist/display-reactor.js +6 -0
- package/dist/display-reactor.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/metadata-display-reactor.d.ts +73 -0
- package/dist/metadata-display-reactor.d.ts.map +1 -0
- package/dist/metadata-display-reactor.js +128 -0
- package/dist/metadata-display-reactor.js.map +1 -0
- package/dist/visitor/arguments/index.d.ts +69 -0
- package/dist/visitor/arguments/index.d.ts.map +1 -0
- package/dist/visitor/arguments/index.js +277 -0
- package/dist/visitor/arguments/index.js.map +1 -0
- package/dist/visitor/arguments/types.d.ts +92 -0
- package/dist/visitor/arguments/types.d.ts.map +1 -0
- package/dist/visitor/arguments/types.js +2 -0
- package/dist/visitor/arguments/types.js.map +1 -0
- package/dist/visitor/constants.d.ts +4 -0
- package/dist/visitor/constants.d.ts.map +1 -0
- package/dist/visitor/constants.js +61 -0
- package/dist/visitor/constants.js.map +1 -0
- package/dist/visitor/helpers.d.ts +30 -0
- package/dist/visitor/helpers.d.ts.map +1 -0
- package/dist/visitor/helpers.js +200 -0
- package/dist/visitor/helpers.js.map +1 -0
- package/dist/visitor/returns/index.d.ts +76 -0
- package/dist/visitor/returns/index.d.ts.map +1 -0
- package/dist/visitor/returns/index.js +425 -0
- package/dist/visitor/returns/index.js.map +1 -0
- package/dist/visitor/returns/types.d.ts +142 -0
- package/dist/visitor/returns/types.d.ts.map +1 -0
- package/dist/visitor/returns/types.js +2 -0
- package/dist/visitor/returns/types.js.map +1 -0
- package/dist/visitor/types.d.ts +6 -0
- package/dist/visitor/types.d.ts.map +1 -0
- package/dist/visitor/types.js +3 -0
- package/dist/visitor/types.js.map +1 -0
- package/package.json +4 -2
- package/src/adapter.ts +446 -0
- package/src/constants.ts +11 -0
- package/src/display-reactor.ts +332 -0
- package/src/index.ts +7 -0
- package/src/metadata-display-reactor.ts +184 -0
- package/src/reactor.ts +199 -0
- package/src/types.ts +107 -0
- package/src/utils.ts +28 -0
- package/src/visitor/arguments/index.test.ts +882 -0
- package/src/visitor/arguments/index.ts +405 -0
- package/src/visitor/arguments/types.ts +168 -0
- package/src/visitor/constants.ts +62 -0
- package/src/visitor/helpers.ts +221 -0
- package/src/visitor/returns/index.test.ts +2027 -0
- package/src/visitor/returns/index.ts +545 -0
- package/src/visitor/returns/types.ts +271 -0
- package/src/visitor/types.ts +29 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseActor,
|
|
3
|
+
DisplayReactorParameters,
|
|
4
|
+
TransformKey,
|
|
5
|
+
} from "@ic-reactor/core"
|
|
6
|
+
import type {
|
|
7
|
+
CandidDisplayReactorParameters,
|
|
8
|
+
DynamicMethodOptions,
|
|
9
|
+
} from "./types"
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
DisplayReactor,
|
|
13
|
+
didToDisplayCodec,
|
|
14
|
+
didTypeFromArray,
|
|
15
|
+
} from "@ic-reactor/core"
|
|
16
|
+
import { CandidAdapter } from "./adapter"
|
|
17
|
+
import { IDL } from "@icp-sdk/core/candid"
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// CandidDisplayReactor
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* CandidDisplayReactor combines the display transformation capabilities of
|
|
25
|
+
* DisplayReactor with dynamic Candid parsing from CandidReactor.
|
|
26
|
+
*
|
|
27
|
+
* This class provides:
|
|
28
|
+
* - **Display transformations**: Automatic type conversion between Candid and
|
|
29
|
+
* display-friendly types (bigint ↔ string, Principal ↔ string, etc.)
|
|
30
|
+
* - **Validation**: Optional argument validation with display types
|
|
31
|
+
* - **Dynamic Candid parsing**: Initialize from Candid source or fetch from network
|
|
32
|
+
* - **Dynamic method registration**: Register methods at runtime with Candid signatures
|
|
33
|
+
*
|
|
34
|
+
* @typeParam A - The actor service type
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { CandidDisplayReactor } from "@ic-reactor/candid"
|
|
39
|
+
*
|
|
40
|
+
* const reactor = new CandidDisplayReactor({
|
|
41
|
+
* clientManager,
|
|
42
|
+
* canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai",
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* // Initialize from network (fetches Candid from canister)
|
|
46
|
+
* await reactor.initialize()
|
|
47
|
+
*
|
|
48
|
+
* // Or provide Candid source directly
|
|
49
|
+
* const reactor2 = new CandidDisplayReactor({
|
|
50
|
+
* clientManager,
|
|
51
|
+
* canisterId: "...",
|
|
52
|
+
* candid: `service : { greet : (text) -> (text) query }`
|
|
53
|
+
* })
|
|
54
|
+
* await reactor2.initialize()
|
|
55
|
+
*
|
|
56
|
+
* // Call methods with display types (strings instead of bigint/Principal)
|
|
57
|
+
* const result = await reactor.callMethod({
|
|
58
|
+
* functionName: "transfer",
|
|
59
|
+
* args: [{ to: "aaaaa-aa", amount: "1000000" }] // strings!
|
|
60
|
+
* })
|
|
61
|
+
*
|
|
62
|
+
* // Add validation
|
|
63
|
+
* reactor.registerValidator("transfer", ([input]) => {
|
|
64
|
+
* if (!input.to) {
|
|
65
|
+
* return { success: false, issues: [{ path: ["to"], message: "Required" }] }
|
|
66
|
+
* }
|
|
67
|
+
* return { success: true }
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export class CandidDisplayReactor<
|
|
72
|
+
A = BaseActor,
|
|
73
|
+
T extends TransformKey = "display",
|
|
74
|
+
> extends DisplayReactor<A, T> {
|
|
75
|
+
public readonly transform = "display" as T
|
|
76
|
+
public adapter: CandidAdapter
|
|
77
|
+
private candidSource?: string
|
|
78
|
+
|
|
79
|
+
constructor(config: CandidDisplayReactorParameters<A>) {
|
|
80
|
+
const superConfig = { ...config }
|
|
81
|
+
|
|
82
|
+
if (!superConfig.idlFactory) {
|
|
83
|
+
superConfig.idlFactory = ({ IDL }) => IDL.Service({})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
super(superConfig as DisplayReactorParameters<A>)
|
|
87
|
+
|
|
88
|
+
this.candidSource = config.candid
|
|
89
|
+
|
|
90
|
+
if (config.adapter) {
|
|
91
|
+
this.adapter = config.adapter
|
|
92
|
+
} else {
|
|
93
|
+
this.adapter = new CandidAdapter({
|
|
94
|
+
clientManager: this.clientManager,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// INITIALIZATION
|
|
101
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initializes the reactor by parsing the provided Candid string or fetching it from the network.
|
|
105
|
+
* This updates the internal service definition with the actual canister interface.
|
|
106
|
+
*
|
|
107
|
+
* After initialization, all DisplayReactor methods work with display type transformations.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const reactor = new CandidDisplayReactor({
|
|
112
|
+
* clientManager,
|
|
113
|
+
* canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai",
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* // Fetches Candid from the canister and initializes
|
|
117
|
+
* await reactor.initialize()
|
|
118
|
+
*
|
|
119
|
+
* // Now you can call methods with display types
|
|
120
|
+
* const balance = await reactor.callMethod({
|
|
121
|
+
* functionName: "icrc1_balance_of",
|
|
122
|
+
* args: [{ owner: "aaaaa-aa" }] // Principal as string!
|
|
123
|
+
* })
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
public async initialize(): Promise<void> {
|
|
127
|
+
let idlFactory: IDL.InterfaceFactory
|
|
128
|
+
|
|
129
|
+
if (this.candidSource) {
|
|
130
|
+
const definition = await this.adapter.parseCandidSource(this.candidSource)
|
|
131
|
+
idlFactory = definition.idlFactory
|
|
132
|
+
} else {
|
|
133
|
+
const definition = await this.adapter.getCandidDefinition(this.canisterId)
|
|
134
|
+
idlFactory = definition.idlFactory
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.service = idlFactory({ IDL })
|
|
138
|
+
|
|
139
|
+
// Re-initialize codecs after service is updated
|
|
140
|
+
this.reinitializeCodecs()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Re-initialize the display codecs after the service has been updated.
|
|
145
|
+
* This is called automatically after initialize() or registerMethod().
|
|
146
|
+
*/
|
|
147
|
+
private reinitializeCodecs(): void {
|
|
148
|
+
const fields = this.getServiceInterface()?._fields
|
|
149
|
+
if (!fields) return
|
|
150
|
+
|
|
151
|
+
// Access the private codecs map from DisplayReactor
|
|
152
|
+
const codecs = (this as any).codecs as Map<
|
|
153
|
+
string,
|
|
154
|
+
{ args: any; result: any }
|
|
155
|
+
>
|
|
156
|
+
|
|
157
|
+
for (const [methodName, funcType] of fields) {
|
|
158
|
+
// Skip if already exists
|
|
159
|
+
if (codecs.has(methodName)) continue
|
|
160
|
+
|
|
161
|
+
const argsIdlType = didTypeFromArray(funcType.argTypes)
|
|
162
|
+
const retIdlType = didTypeFromArray(funcType.retTypes)
|
|
163
|
+
|
|
164
|
+
codecs.set(methodName, {
|
|
165
|
+
args: didToDisplayCodec(argsIdlType),
|
|
166
|
+
result: didToDisplayCodec(retIdlType),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
172
|
+
// DYNAMIC METHOD REGISTRATION
|
|
173
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Register a dynamic method by its Candid signature.
|
|
177
|
+
* After registration, all DisplayReactor methods work with display type transformations.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // Register a method
|
|
182
|
+
* await reactor.registerMethod({
|
|
183
|
+
* functionName: "icrc1_balance_of",
|
|
184
|
+
* candid: "(record { owner : principal }) -> (nat) query"
|
|
185
|
+
* })
|
|
186
|
+
*
|
|
187
|
+
* // Now use with display types!
|
|
188
|
+
* const balance = await reactor.callMethod({
|
|
189
|
+
* functionName: "icrc1_balance_of",
|
|
190
|
+
* args: [{ owner: "aaaaa-aa" }] // Principal as string
|
|
191
|
+
* })
|
|
192
|
+
* // balance is string (not bigint) due to display transformation
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
public async registerMethod(options: DynamicMethodOptions): Promise<void> {
|
|
196
|
+
const { functionName, candid } = options
|
|
197
|
+
|
|
198
|
+
// Check if method already registered
|
|
199
|
+
const existing = this.service._fields.find(
|
|
200
|
+
([name]) => name === functionName
|
|
201
|
+
)
|
|
202
|
+
if (existing) return
|
|
203
|
+
|
|
204
|
+
// Parse the Candid signature
|
|
205
|
+
const serviceSource = candid.includes("service :")
|
|
206
|
+
? candid
|
|
207
|
+
: `service : { ${functionName} : ${candid}; }`
|
|
208
|
+
|
|
209
|
+
const { idlFactory } = await this.adapter.parseCandidSource(serviceSource)
|
|
210
|
+
const parsedService = idlFactory({ IDL })
|
|
211
|
+
|
|
212
|
+
const funcField = parsedService._fields.find(
|
|
213
|
+
([name]) => name === functionName
|
|
214
|
+
)
|
|
215
|
+
if (!funcField) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Method "${functionName}" not found in the provided Candid signature`
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Inject into our service
|
|
222
|
+
this.service._fields.push(funcField)
|
|
223
|
+
|
|
224
|
+
// Re-initialize codecs for the new method
|
|
225
|
+
this.reinitializeCodecs()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Register multiple methods at once.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* await reactor.registerMethods([
|
|
234
|
+
* { functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query" },
|
|
235
|
+
* { functionName: "icrc1_transfer", candid: "(record { to : principal; amount : nat }) -> (variant { Ok : nat; Err : text })" }
|
|
236
|
+
* ])
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
public async registerMethods(methods: DynamicMethodOptions[]): Promise<void> {
|
|
240
|
+
await Promise.all(methods.map((m) => this.registerMethod(m)))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if a method is registered (either from initialize or registerMethod).
|
|
245
|
+
*/
|
|
246
|
+
public hasMethod(functionName: string): boolean {
|
|
247
|
+
return this.service._fields.some(([name]) => name === functionName)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get all registered method names.
|
|
252
|
+
*/
|
|
253
|
+
public getMethodNames(): string[] {
|
|
254
|
+
return this.service._fields.map(([name]) => name)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
258
|
+
// DYNAMIC CALL SHORTCUTS
|
|
259
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Perform a dynamic update call in one step with display type transformations.
|
|
263
|
+
* Registers the method if not already registered, then calls it.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const result = await reactor.callDynamic({
|
|
268
|
+
* functionName: "transfer",
|
|
269
|
+
* candid: "(record { to : principal; amount : nat }) -> (variant { Ok : nat; Err : text })",
|
|
270
|
+
* args: [{ to: "aaaaa-aa", amount: "100" }] // Display types!
|
|
271
|
+
* })
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
public async callDynamic<T = unknown>(
|
|
275
|
+
options: DynamicMethodOptions & { args?: unknown[] }
|
|
276
|
+
): Promise<T> {
|
|
277
|
+
await this.registerMethod(options)
|
|
278
|
+
return this.callMethod({
|
|
279
|
+
functionName: options.functionName as any,
|
|
280
|
+
args: options.args as any,
|
|
281
|
+
}) as T
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Perform a dynamic query call in one step with display type transformations.
|
|
286
|
+
* Registers the method if not already registered, then calls it.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* const balance = await reactor.queryDynamic({
|
|
291
|
+
* functionName: "icrc1_balance_of",
|
|
292
|
+
* candid: "(record { owner : principal }) -> (nat) query",
|
|
293
|
+
* args: [{ owner: "aaaaa-aa" }] // Display types!
|
|
294
|
+
* })
|
|
295
|
+
* // balance is string (not BigInt)
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
public async queryDynamic<T = unknown>(
|
|
299
|
+
options: DynamicMethodOptions & { args?: unknown[] }
|
|
300
|
+
): Promise<T> {
|
|
301
|
+
await this.registerMethod(options)
|
|
302
|
+
return this.callMethod({
|
|
303
|
+
functionName: options.functionName as any,
|
|
304
|
+
args: options.args as any,
|
|
305
|
+
}) as T
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Fetch with dynamic Candid and TanStack Query caching.
|
|
310
|
+
* Registers the method if not already registered, then fetches with caching.
|
|
311
|
+
* Results are transformed to display types.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```typescript
|
|
315
|
+
* const balance = await reactor.fetchQueryDynamic({
|
|
316
|
+
* functionName: "icrc1_balance_of",
|
|
317
|
+
* candid: "(record { owner : principal }) -> (nat) query",
|
|
318
|
+
* args: [{ owner: "aaaaa-aa" }]
|
|
319
|
+
* })
|
|
320
|
+
* // Subsequent calls with same args return cached result
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
public async fetchQueryDynamic<T = unknown>(
|
|
324
|
+
options: DynamicMethodOptions & { args?: unknown[] }
|
|
325
|
+
): Promise<T> {
|
|
326
|
+
await this.registerMethod(options)
|
|
327
|
+
return this.fetchQuery({
|
|
328
|
+
functionName: options.functionName as any,
|
|
329
|
+
args: options.args as any,
|
|
330
|
+
}) as T
|
|
331
|
+
}
|
|
332
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActorMethodReturnType,
|
|
3
|
+
BaseActor,
|
|
4
|
+
FunctionName,
|
|
5
|
+
} from "@ic-reactor/core"
|
|
6
|
+
import { CandidDisplayReactor } from "./display-reactor"
|
|
7
|
+
import type {
|
|
8
|
+
CandidDisplayReactorParameters,
|
|
9
|
+
DynamicMethodOptions,
|
|
10
|
+
} from "./types"
|
|
11
|
+
import {
|
|
12
|
+
ArgumentFieldVisitor,
|
|
13
|
+
MethodArgumentsMeta,
|
|
14
|
+
ServiceArgumentsMeta,
|
|
15
|
+
} from "./visitor/arguments"
|
|
16
|
+
import {
|
|
17
|
+
MethodResultMeta,
|
|
18
|
+
ResolvedMethodResult,
|
|
19
|
+
ResultFieldVisitor,
|
|
20
|
+
ServiceResultMeta,
|
|
21
|
+
} from "./visitor/returns"
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// MetadataDisplayReactor
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* MetadataDisplayReactor combines visitor-based metadata generation
|
|
29
|
+
* for both input forms and result display.
|
|
30
|
+
*
|
|
31
|
+
* ## Architecture
|
|
32
|
+
*
|
|
33
|
+
* It extends the base Reactor and adds metadata generation capabilities.
|
|
34
|
+
* Unlike DisplayReactor, it does not use a separate codec for transformation.
|
|
35
|
+
* Instead, it uses the metadata visitor to resolve raw values into display-ready structures.
|
|
36
|
+
*/
|
|
37
|
+
declare module "@ic-reactor/core" {
|
|
38
|
+
interface TransformArgsRegistry<T> {
|
|
39
|
+
metadata: TransformArgsRegistry<T>["display"]
|
|
40
|
+
}
|
|
41
|
+
interface TransformReturnRegistry<T, A = BaseActor> {
|
|
42
|
+
metadata: ResolvedMethodResult<A>
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class MetadataDisplayReactor<A = BaseActor> extends CandidDisplayReactor<
|
|
47
|
+
A,
|
|
48
|
+
"metadata"
|
|
49
|
+
> {
|
|
50
|
+
public override readonly transform = "metadata" as const
|
|
51
|
+
|
|
52
|
+
// Metadata storage
|
|
53
|
+
private argumentMeta: ServiceArgumentsMeta<A> | null = null
|
|
54
|
+
private resultMeta: ServiceResultMeta<A> | null = null
|
|
55
|
+
|
|
56
|
+
// Visitors (stateless, can be reused)
|
|
57
|
+
private static argVisitor = new ArgumentFieldVisitor()
|
|
58
|
+
private static resultVisitor = new ResultFieldVisitor()
|
|
59
|
+
|
|
60
|
+
constructor(config: CandidDisplayReactorParameters<A>) {
|
|
61
|
+
super(config)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
65
|
+
// INITIALIZATION
|
|
66
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initializes the reactor by parsing Candid and generating all metadata.
|
|
70
|
+
*/
|
|
71
|
+
public override async initialize(): Promise<void> {
|
|
72
|
+
await super.initialize()
|
|
73
|
+
|
|
74
|
+
// Generate metadata using visitors
|
|
75
|
+
this.generateMetadata()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate all metadata from the service interface using visitors.
|
|
80
|
+
*/
|
|
81
|
+
private generateMetadata(): void {
|
|
82
|
+
const service = this.getServiceInterface()
|
|
83
|
+
if (!service) return
|
|
84
|
+
|
|
85
|
+
// Generate argument metadata
|
|
86
|
+
this.argumentMeta = service.accept(
|
|
87
|
+
MetadataDisplayReactor.argVisitor,
|
|
88
|
+
null as any
|
|
89
|
+
) as ServiceArgumentsMeta<A>
|
|
90
|
+
|
|
91
|
+
// Generate result metadata
|
|
92
|
+
this.resultMeta = service.accept(
|
|
93
|
+
MetadataDisplayReactor.resultVisitor,
|
|
94
|
+
null as any
|
|
95
|
+
) as ServiceResultMeta<A>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
// METADATA ACCESS
|
|
100
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
101
|
+
/**
|
|
102
|
+
* Get argument field metadata for a method.
|
|
103
|
+
* Use this to generate input forms.
|
|
104
|
+
*/
|
|
105
|
+
public getArgumentMeta<M extends FunctionName<A>>(
|
|
106
|
+
methodName: M
|
|
107
|
+
): MethodArgumentsMeta<A, M> | undefined {
|
|
108
|
+
return this.argumentMeta?.[methodName]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get result field metadata for a method.
|
|
113
|
+
* Use this to render results.
|
|
114
|
+
*/
|
|
115
|
+
public getResultMeta<M extends FunctionName<A>>(
|
|
116
|
+
methodName: M
|
|
117
|
+
): MethodResultMeta<A, M> | undefined {
|
|
118
|
+
return this.resultMeta?.[methodName]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all argument metadata.
|
|
123
|
+
*/
|
|
124
|
+
public getAllArgumentMeta(): ServiceArgumentsMeta<A> | null {
|
|
125
|
+
return this.argumentMeta
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get all result metadata.
|
|
130
|
+
*/
|
|
131
|
+
public getAllResultMeta(): ServiceResultMeta<A> | null {
|
|
132
|
+
return this.resultMeta
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
136
|
+
// DYNAMIC METHOD REGISTRATION
|
|
137
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
138
|
+
/**
|
|
139
|
+
* Register a dynamic method by its Candid signature.
|
|
140
|
+
* After registration, all DisplayReactor methods work with display type transformations.
|
|
141
|
+
*/
|
|
142
|
+
public override async registerMethod(
|
|
143
|
+
options: DynamicMethodOptions
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
await super.registerMethod(options)
|
|
146
|
+
|
|
147
|
+
// Regenerate metadata
|
|
148
|
+
this.generateMetadata()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
152
|
+
// DYNAMIC CALL SHORTCUTS
|
|
153
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
154
|
+
protected override transformResult<M extends FunctionName<A>>(
|
|
155
|
+
methodName: M,
|
|
156
|
+
result: ActorMethodReturnType<A[M]>
|
|
157
|
+
): ResolvedMethodResult<A> {
|
|
158
|
+
// Get metadata and generate resolved result
|
|
159
|
+
const meta = this.getResultMeta(methodName)
|
|
160
|
+
if (!meta) {
|
|
161
|
+
throw new Error(`No metadata found for method "${methodName}"`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return meta.generateMetadata(result) as ResolvedMethodResult<A>
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Perform a dynamic call and return result with metadata.
|
|
169
|
+
*/
|
|
170
|
+
public async callDynamicWithMeta<T = unknown>(
|
|
171
|
+
options: DynamicMethodOptions & { args?: unknown[] }
|
|
172
|
+
): Promise<{ result: T; meta: MethodResultMeta<A> }> {
|
|
173
|
+
await this.registerMethod(options)
|
|
174
|
+
|
|
175
|
+
const result = (await this.callMethod({
|
|
176
|
+
functionName: options.functionName as any,
|
|
177
|
+
args: options.args as any,
|
|
178
|
+
})) as T
|
|
179
|
+
|
|
180
|
+
const meta = this.getResultMeta(options.functionName as any)!
|
|
181
|
+
|
|
182
|
+
return { result, meta }
|
|
183
|
+
}
|
|
184
|
+
}
|