@ic-reactor/core 2.0.1 → 3.0.0-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.
- package/README.md +398 -175
- package/dist/client.d.ts +161 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +499 -0
- package/dist/client.js.map +1 -0
- package/dist/display/helper.d.ts +10 -0
- package/dist/display/helper.d.ts.map +1 -0
- package/dist/display/helper.js +67 -0
- package/dist/display/helper.js.map +1 -0
- package/dist/display/index.d.ts +4 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +4 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/types.d.ts +31 -0
- package/dist/display/types.d.ts.map +1 -0
- package/dist/display/types.js +2 -0
- package/dist/display/types.js.map +1 -0
- package/dist/display/visitor.d.ts +28 -0
- package/dist/display/visitor.d.ts.map +1 -0
- package/dist/display/visitor.js +318 -0
- package/dist/display/visitor.js.map +1 -0
- package/dist/display-reactor.d.ts +245 -0
- package/dist/display-reactor.d.ts.map +1 -0
- package/dist/display-reactor.js +331 -0
- package/dist/display-reactor.js.map +1 -0
- package/dist/errors/index.d.ts +118 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +204 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -47
- package/dist/index.js.map +1 -0
- package/dist/reactor.d.ts +133 -0
- package/dist/reactor.d.ts.map +1 -0
- package/dist/reactor.js +325 -0
- package/dist/reactor.js.map +1 -0
- package/dist/types/client.d.ts +89 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +2 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/reactor.d.ts +117 -0
- package/dist/types/reactor.d.ts.map +1 -0
- package/dist/types/reactor.js +2 -0
- package/dist/types/reactor.js.map +1 -0
- package/dist/types/result.d.ts +48 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +2 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/transform.d.ts +7 -0
- package/dist/types/transform.d.ts.map +1 -0
- package/dist/types/transform.js +2 -0
- package/dist/types/transform.js.map +1 -0
- package/dist/types/variant.d.ts +18 -0
- package/dist/types/variant.d.ts.map +1 -0
- package/dist/types/variant.js +2 -0
- package/dist/types/variant.js.map +1 -0
- package/dist/utils/agent.d.ts +30 -1
- package/dist/utils/agent.d.ts.map +1 -0
- package/dist/utils/agent.js +118 -16
- package/dist/utils/agent.js.map +1 -0
- package/dist/utils/candid.d.ts +39 -1
- package/dist/utils/candid.d.ts.map +1 -0
- package/dist/utils/candid.js +76 -16
- package/dist/utils/candid.js.map +1 -0
- package/dist/utils/constants.d.ts +3 -4
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +7 -11
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/helper.d.ts +16 -39
- package/dist/utils/helper.d.ts.map +1 -0
- package/dist/utils/helper.js +53 -155
- package/dist/utils/helper.js.map +1 -0
- package/dist/utils/index.d.ts +4 -5
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -49
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/polling.d.ts +176 -0
- package/dist/utils/polling.d.ts.map +1 -0
- package/dist/utils/polling.js +170 -0
- package/dist/utils/polling.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +65 -39
- package/LICENSE.md +0 -8
- package/dist/classes/actor/index.d.ts +0 -34
- package/dist/classes/actor/index.js +0 -245
- package/dist/classes/actor/types.d.ts +0 -113
- package/dist/classes/actor/types.js +0 -2
- package/dist/classes/adapter/index.d.ts +0 -19
- package/dist/classes/adapter/index.js +0 -140
- package/dist/classes/adapter/types.d.ts +0 -14
- package/dist/classes/adapter/types.js +0 -2
- package/dist/classes/agent/index.d.ts +0 -37
- package/dist/classes/agent/index.js +0 -221
- package/dist/classes/agent/types.d.ts +0 -87
- package/dist/classes/agent/types.js +0 -2
- package/dist/classes/index.d.ts +0 -3
- package/dist/classes/index.js +0 -19
- package/dist/classes/types.d.ts +0 -15
- package/dist/classes/types.js +0 -20
- package/dist/createActorManager.d.ts +0 -12
- package/dist/createActorManager.js +0 -17
- package/dist/createAgentManager.d.ts +0 -12
- package/dist/createAgentManager.js +0 -17
- package/dist/createCandidAdapter.d.ts +0 -11
- package/dist/createCandidAdapter.js +0 -16
- package/dist/createReactorCore.d.ts +0 -10
- package/dist/createReactorCore.js +0 -112
- package/dist/createReactorStore.d.ts +0 -11
- package/dist/createReactorStore.js +0 -31
- package/dist/types.d.ts +0 -96
- package/dist/types.js +0 -17
- package/dist/utils/hash.d.ts +0 -12
- package/dist/utils/hash.js +0 -70
- package/dist/utils/principal.d.ts +0 -1
- package/dist/utils/principal.js +0 -17
package/README.md
CHANGED
|
@@ -1,283 +1,506 @@
|
|
|
1
|
-
|
|
1
|
+
# @ic-reactor/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
<strong>The Core Library for Internet Computer Applications</strong>
|
|
5
|
+
<br><br>
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@ic-reactor/core)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
</div>
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
---
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
Framework-agnostic core library for building type-safe Internet Computer applications with [TanStack Query](https://tanstack.com/query) integration.
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
> **Note**: For React applications, use [`@ic-reactor/react`](../react) instead, which re-exports everything from this package plus React-specific hooks.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
12
19
|
|
|
13
|
-
**
|
|
20
|
+
- 🔒 **End-to-End Type Safety** — From Candid to your application
|
|
21
|
+
- ⚡ **TanStack Query Integration** — Automatic caching, background refetching, optimistic updates
|
|
22
|
+
- 🔄 **Auto Transformations** — `DisplayReactor` converts BigInt to string, Principal to text
|
|
23
|
+
- 📦 **Result Unwrapping** — Automatic `Ok`/`Err` handling from Candid Result types
|
|
24
|
+
- 🔐 **Internet Identity** — Built-in authentication with session restoration
|
|
25
|
+
- 🏗️ **Multi-Canister Support** — Shared authentication across canisters
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
14
28
|
|
|
15
29
|
```bash
|
|
16
|
-
|
|
30
|
+
# Core library
|
|
31
|
+
npm install @ic-reactor/core @icp-sdk/core @tanstack/query-core
|
|
32
|
+
|
|
33
|
+
# Optional: For Internet Identity authentication
|
|
34
|
+
npm install @icp-sdk/auth
|
|
17
35
|
```
|
|
18
36
|
|
|
19
|
-
|
|
37
|
+
## Core Concepts
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
### Architecture Overview
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
┌─────────────────┐ ┌──────────────┐ ┌─────────────────────┐
|
|
43
|
+
│ ClientManager │───▶│ Reactor │───▶│ TanStack Query │
|
|
44
|
+
│ (Agent + Auth) │ │ (Canister) │ │ (Caching Layer) │
|
|
45
|
+
└─────────────────┘ └──────────────┘ └─────────────────────┘
|
|
46
|
+
│ │
|
|
47
|
+
│ ┌─────▼─────┐
|
|
48
|
+
│ │ Display │
|
|
49
|
+
│ │ Reactor │
|
|
50
|
+
│ └───────────┘
|
|
51
|
+
│ (Type Transforms)
|
|
52
|
+
▼
|
|
53
|
+
┌─────────────────┐
|
|
54
|
+
│ Internet │
|
|
55
|
+
│ Identity │
|
|
56
|
+
└─────────────────┘
|
|
23
57
|
```
|
|
24
58
|
|
|
25
|
-
|
|
59
|
+
## Quick Start
|
|
26
60
|
|
|
27
|
-
|
|
61
|
+
### 1. Create ClientManager
|
|
28
62
|
|
|
29
|
-
|
|
63
|
+
The `ClientManager` handles the IC agent, authentication, and query client:
|
|
30
64
|
|
|
31
65
|
```typescript
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
66
|
+
import { ClientManager } from "@ic-reactor/core"
|
|
67
|
+
import { QueryClient } from "@tanstack/query-core"
|
|
34
68
|
|
|
35
|
-
|
|
69
|
+
const queryClient = new QueryClient({
|
|
70
|
+
defaultOptions: {
|
|
71
|
+
queries: { staleTime: 5 * 60 * 1000 }, // 5 minutes
|
|
72
|
+
},
|
|
73
|
+
})
|
|
36
74
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
75
|
+
const clientManager = new ClientManager({
|
|
76
|
+
queryClient,
|
|
77
|
+
withProcessEnv: true, // Reads DFX_NETWORK from environment
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Initialize agent and restore session
|
|
81
|
+
await clientManager.initialize()
|
|
43
82
|
```
|
|
44
83
|
|
|
45
|
-
|
|
84
|
+
### 2. Create Reactor
|
|
85
|
+
|
|
86
|
+
The `Reactor` class wraps a canister with type-safe methods and caching:
|
|
46
87
|
|
|
47
88
|
```typescript
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
89
|
+
import { Reactor } from "@ic-reactor/core"
|
|
90
|
+
import { idlFactory, type _SERVICE } from "./declarations/my_canister"
|
|
91
|
+
|
|
92
|
+
const backend = new Reactor<_SERVICE>({
|
|
93
|
+
clientManager,
|
|
94
|
+
idlFactory,
|
|
95
|
+
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
56
96
|
})
|
|
97
|
+
```
|
|
57
98
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
### 3. Call Methods
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Direct call (no caching)
|
|
103
|
+
const greeting = await backend.callMethod({
|
|
104
|
+
functionName: "greet",
|
|
105
|
+
args: ["World"],
|
|
62
106
|
})
|
|
63
107
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
functionName: "icrc1_transfer",
|
|
69
|
-
args: [
|
|
70
|
-
{
|
|
71
|
-
to: { owner: getPrincipal(), subaccount: [] },
|
|
72
|
-
amount: BigInt(10000000000),
|
|
73
|
-
fee: [],
|
|
74
|
-
memo: [],
|
|
75
|
-
created_at_time: [],
|
|
76
|
-
from_subaccount: [],
|
|
77
|
-
},
|
|
78
|
-
],
|
|
108
|
+
// Fetch with caching
|
|
109
|
+
const cachedGreeting = await backend.fetchQuery({
|
|
110
|
+
functionName: "greet",
|
|
111
|
+
args: ["World"],
|
|
79
112
|
})
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
|
|
114
|
+
// Get from cache (no network call)
|
|
115
|
+
const fromCache = backend.getQueryData({
|
|
116
|
+
functionName: "greet",
|
|
117
|
+
args: ["World"],
|
|
83
118
|
})
|
|
84
119
|
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
// Invalidate cache
|
|
121
|
+
backend.invalidateQueries({ functionName: "greet" })
|
|
87
122
|
```
|
|
88
123
|
|
|
89
|
-
|
|
124
|
+
## ClientManager API
|
|
125
|
+
|
|
126
|
+
### Constructor Options
|
|
90
127
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
interface ClientManagerParameters {
|
|
130
|
+
queryClient: QueryClient // TanStack Query client
|
|
131
|
+
port?: number // Local replica port (default: 4943)
|
|
132
|
+
withLocalEnv?: boolean // Force local network
|
|
133
|
+
withProcessEnv?: boolean // Read DFX_NETWORK from env
|
|
134
|
+
agentOptions?: HttpAgentOptions // Custom agent options
|
|
135
|
+
authClient?: AuthClient // Pre-configured auth client
|
|
136
|
+
}
|
|
137
|
+
```
|
|
94
138
|
|
|
95
|
-
|
|
139
|
+
### Authentication Methods
|
|
96
140
|
|
|
97
141
|
```typescript
|
|
98
|
-
// agent
|
|
99
|
-
|
|
142
|
+
// Initialize agent and restore previous session
|
|
143
|
+
await clientManager.initialize()
|
|
144
|
+
|
|
145
|
+
// Trigger login flow (opens Internet Identity)
|
|
146
|
+
await clientManager.login({
|
|
147
|
+
identityProvider: "https://identity.ic0.app", // optional, auto-detected
|
|
148
|
+
onSuccess: () => console.log("Logged in!"),
|
|
149
|
+
onError: (error) => console.error(error),
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Logout and revert to anonymous identity
|
|
153
|
+
await clientManager.logout()
|
|
100
154
|
|
|
101
|
-
|
|
155
|
+
// Manually authenticate (restore session)
|
|
156
|
+
const identity = await clientManager.authenticate()
|
|
102
157
|
```
|
|
103
158
|
|
|
104
|
-
|
|
159
|
+
### State Subscriptions
|
|
105
160
|
|
|
106
161
|
```typescript
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
import * as candidB from "./declarations/candidB"
|
|
111
|
-
import { agentManager } from "./agent"
|
|
112
|
-
|
|
113
|
-
type CandidA = typeof candidA.candidA
|
|
114
|
-
type CandidB = typeof candidB.candidB
|
|
115
|
-
|
|
116
|
-
const actorA = createActorManager<CandidA>({
|
|
117
|
-
agentManager,
|
|
118
|
-
canisterId: candidA.canisterId,
|
|
119
|
-
idlFactory: candidA.idlFactory,
|
|
162
|
+
// Subscribe to agent state changes
|
|
163
|
+
const unsubAgent = clientManager.subscribeAgentState((state) => {
|
|
164
|
+
console.log("Agent state:", state.isInitialized, state.network)
|
|
120
165
|
})
|
|
121
166
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
idlFactory: candidB.idlFactory,
|
|
167
|
+
// Subscribe to auth state changes
|
|
168
|
+
const unsubAuth = clientManager.subscribeAuthState((state) => {
|
|
169
|
+
console.log("Auth state:", state.isAuthenticated, state.identity)
|
|
126
170
|
})
|
|
171
|
+
|
|
172
|
+
// Subscribe to identity changes
|
|
173
|
+
const unsubIdentity = clientManager.subscribe((identity) => {
|
|
174
|
+
console.log("New identity:", identity.getPrincipal().toText())
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Cleanup
|
|
178
|
+
unsubAgent()
|
|
179
|
+
unsubAuth()
|
|
180
|
+
unsubIdentity()
|
|
127
181
|
```
|
|
128
182
|
|
|
129
|
-
|
|
183
|
+
### Properties
|
|
130
184
|
|
|
131
185
|
```typescript
|
|
132
|
-
|
|
133
|
-
|
|
186
|
+
clientManager.agent // HttpAgent instance
|
|
187
|
+
clientManager.agentState // { isInitialized, isInitializing, error, network, isLocalhost }
|
|
188
|
+
clientManager.authState // { identity, isAuthenticated, isAuthenticating, error }
|
|
189
|
+
clientManager.queryClient // TanStack QueryClient
|
|
190
|
+
clientManager.network // "ic" | "local"
|
|
191
|
+
clientManager.isLocal // boolean
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Reactor API
|
|
195
|
+
|
|
196
|
+
### Constructor Options
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
interface ReactorParameters<A> {
|
|
200
|
+
clientManager: ClientManager
|
|
201
|
+
idlFactory: IDL.InterfaceFactory
|
|
202
|
+
canisterId: string | Principal
|
|
203
|
+
name?: string // Optional display name
|
|
204
|
+
pollingOptions?: PollingOptions // Custom polling for update calls
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Core Methods
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// Call a canister method (auto-detects query vs update)
|
|
212
|
+
const result = await reactor.callMethod({
|
|
213
|
+
functionName: "my_method",
|
|
214
|
+
args: [arg1, arg2],
|
|
215
|
+
callConfig: { effectiveCanisterId: ... }, // optional
|
|
134
216
|
})
|
|
135
|
-
console.log("Response from CanisterA method:", await version)
|
|
136
217
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
218
|
+
// Fetch and cache data
|
|
219
|
+
const data = await reactor.fetchQuery({
|
|
220
|
+
functionName: "get_data",
|
|
221
|
+
args: [],
|
|
140
222
|
})
|
|
141
|
-
|
|
223
|
+
|
|
224
|
+
// Get cached data (synchronous, no network)
|
|
225
|
+
const cached = reactor.getQueryData({
|
|
226
|
+
functionName: "get_data",
|
|
227
|
+
args: [],
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Invalidate cached queries
|
|
231
|
+
reactor.invalidateQueries() // all queries for this canister
|
|
232
|
+
reactor.invalidateQueries({ functionName: "get_data" }) // specific method
|
|
233
|
+
reactor.invalidateQueries({ functionName: "get_user", args: ["user-1"] }) // specific args
|
|
234
|
+
|
|
235
|
+
// Get query options for TanStack Query
|
|
236
|
+
const options = reactor.getQueryOptions({ functionName: "get_data" })
|
|
142
237
|
```
|
|
143
238
|
|
|
144
|
-
###
|
|
239
|
+
### Properties
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
reactor.canisterId // Principal
|
|
243
|
+
reactor.service // IDL.ServiceClass
|
|
244
|
+
reactor.queryClient // TanStack QueryClient
|
|
245
|
+
reactor.agent // HttpAgent
|
|
246
|
+
reactor.name // string
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## DisplayReactor
|
|
250
|
+
|
|
251
|
+
`DisplayReactor` extends `Reactor` with automatic type transformations for UI-friendly values:
|
|
252
|
+
|
|
253
|
+
### Type Transformations
|
|
145
254
|
|
|
146
|
-
|
|
147
|
-
|
|
255
|
+
| Candid Type | Reactor (raw) | DisplayReactor |
|
|
256
|
+
| -------------------------------- | ------------- | -------------- |
|
|
257
|
+
| `nat`, `int` | `bigint` | `string` |
|
|
258
|
+
| `nat8/16/32/64`, `int8/16/32/64` | `bigint` | `string` |
|
|
259
|
+
| `Principal` | `Principal` | `string` |
|
|
260
|
+
| `vec nat8` (≤32 bytes) | `Uint8Array` | `string` (hex) |
|
|
261
|
+
| `Result<Ok, Err>` | Unwrapped | Unwrapped |
|
|
262
|
+
|
|
263
|
+
### Usage
|
|
148
264
|
|
|
149
265
|
```typescript
|
|
150
|
-
import {
|
|
151
|
-
|
|
266
|
+
import { DisplayReactor } from "@ic-reactor/core"
|
|
267
|
+
|
|
268
|
+
const backend = new DisplayReactor<_SERVICE>({
|
|
269
|
+
clientManager,
|
|
270
|
+
idlFactory,
|
|
271
|
+
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Args and results use display-friendly types
|
|
275
|
+
const balance = await backend.callMethod({
|
|
276
|
+
functionName: "icrc1_balance_of",
|
|
277
|
+
args: [{ owner: "aaaaa-aa", subaccount: [] }], // string instead of Principal
|
|
278
|
+
})
|
|
279
|
+
// balance is "100000000" (string) instead of 100000000n (bigint)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Form Validation
|
|
152
283
|
|
|
153
|
-
|
|
284
|
+
`DisplayReactor` supports validators for mutation arguments:
|
|
154
285
|
|
|
155
|
-
|
|
286
|
+
```typescript
|
|
287
|
+
import { DisplayReactor, ValidationError } from "@ic-reactor/core"
|
|
156
288
|
|
|
157
|
-
|
|
289
|
+
const backend = new DisplayReactor<_SERVICE>({
|
|
290
|
+
clientManager,
|
|
291
|
+
idlFactory,
|
|
292
|
+
canisterId: "...",
|
|
293
|
+
validators: {
|
|
294
|
+
transfer: (args) => {
|
|
295
|
+
const [{ to, amount }] = args
|
|
296
|
+
const issues = []
|
|
297
|
+
|
|
298
|
+
if (!to || to.length < 5) {
|
|
299
|
+
issues.push({ path: ["to"], message: "Invalid recipient" })
|
|
300
|
+
}
|
|
301
|
+
if (!amount || parseFloat(amount) <= 0) {
|
|
302
|
+
issues.push({ path: ["amount"], message: "Amount must be positive" })
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return issues.length > 0 ? { success: false, issues } : { success: true }
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Validate before calling
|
|
311
|
+
const result = await backend.validate("transfer", [{ to: "", amount: "0" }])
|
|
312
|
+
if (!result.success) {
|
|
313
|
+
console.log(result.issues) // [{ path: ["to"], message: "Invalid recipient" }, ...]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Or call with validation (throws ValidationError on failure)
|
|
158
317
|
try {
|
|
159
|
-
|
|
160
|
-
|
|
318
|
+
await backend.callMethodWithValidation({
|
|
319
|
+
functionName: "transfer",
|
|
320
|
+
args: [{ to: "", amount: "0" }],
|
|
321
|
+
})
|
|
161
322
|
} catch (error) {
|
|
162
|
-
|
|
323
|
+
if (error instanceof ValidationError) {
|
|
324
|
+
console.log(error.issues)
|
|
325
|
+
}
|
|
163
326
|
}
|
|
164
327
|
```
|
|
165
328
|
|
|
166
|
-
|
|
329
|
+
## Error Handling
|
|
167
330
|
|
|
168
|
-
|
|
331
|
+
### Error Types
|
|
169
332
|
|
|
170
333
|
```typescript
|
|
171
|
-
import {
|
|
172
|
-
|
|
334
|
+
import {
|
|
335
|
+
CallError,
|
|
336
|
+
CanisterError,
|
|
337
|
+
ValidationError,
|
|
338
|
+
isCallError,
|
|
339
|
+
isCanisterError,
|
|
340
|
+
isValidationError,
|
|
341
|
+
} from "@ic-reactor/core"
|
|
342
|
+
```
|
|
173
343
|
|
|
174
|
-
|
|
344
|
+
| Error Type | Description |
|
|
345
|
+
| ------------------ | -------------------------------------------------------- |
|
|
346
|
+
| `CallError` | Network/agent errors (canister not found, timeout, etc.) |
|
|
347
|
+
| `CanisterError<E>` | Canister returned an `Err` result |
|
|
348
|
+
| `ValidationError` | Argument validation failed (DisplayReactor) |
|
|
175
349
|
|
|
176
|
-
|
|
350
|
+
### Handling Errors
|
|
177
351
|
|
|
178
|
-
|
|
352
|
+
```typescript
|
|
179
353
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
canisterId,
|
|
184
|
-
idlFactory,
|
|
354
|
+
await backend.callMethod({
|
|
355
|
+
functionName: "transfer",
|
|
356
|
+
args: [{ to: principal, amount: 100n }],
|
|
185
357
|
})
|
|
186
|
-
|
|
187
|
-
const name = await callMethod("name")
|
|
188
|
-
console.log(name) // { name: 'Internet Computer' }
|
|
189
358
|
} catch (error) {
|
|
190
|
-
|
|
359
|
+
if (isCanisterError(error)) {
|
|
360
|
+
// Business logic error from canister
|
|
361
|
+
console.log("Canister error:", error.code, error.err)
|
|
362
|
+
// error.err is typed based on your Candid Result type
|
|
363
|
+
} else if (isCallError(error)) {
|
|
364
|
+
// Network/agent error
|
|
365
|
+
console.log("Network error:", error.message)
|
|
366
|
+
} else if (isValidationError(error)) {
|
|
367
|
+
// Validation error (DisplayReactor only)
|
|
368
|
+
console.log("Validation failed:", error.issues)
|
|
369
|
+
}
|
|
191
370
|
}
|
|
192
371
|
```
|
|
193
372
|
|
|
194
|
-
###
|
|
195
|
-
|
|
196
|
-
If you require more control over the state management, you can use the `createReactorStore` function to create a store that provides methods for querying and updating actors.
|
|
373
|
+
### CanisterError Properties
|
|
197
374
|
|
|
198
375
|
```typescript
|
|
199
|
-
|
|
200
|
-
|
|
376
|
+
interface CanisterError<E> {
|
|
377
|
+
err: E // The raw error value from canister
|
|
378
|
+
code: string // Error code (from variant key or "code" field)
|
|
379
|
+
message: string // Human-readable message
|
|
380
|
+
details?: Map<string, string> // Optional details
|
|
381
|
+
}
|
|
382
|
+
```
|
|
201
383
|
|
|
202
|
-
|
|
384
|
+
## Utilities
|
|
203
385
|
|
|
204
|
-
|
|
205
|
-
canisterId,
|
|
206
|
-
idlFactory,
|
|
207
|
-
})
|
|
386
|
+
### Result Unwrapping
|
|
208
387
|
|
|
209
|
-
|
|
210
|
-
await agentManager.authenticate()
|
|
211
|
-
const authClient = agentManager.getAuth()
|
|
388
|
+
Results are automatically unwrapped. The `extractOkResult` utility handles both uppercase (`Ok`/`Err`) and lowercase (`ok`/`err`) variants:
|
|
212
389
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
390
|
+
```typescript
|
|
391
|
+
import { extractOkResult } from "@ic-reactor/core"
|
|
392
|
+
|
|
393
|
+
// Candid: Result<Text, TransferError>
|
|
394
|
+
// Returns the Ok value or throws CanisterError with the Err value
|
|
395
|
+
const result = extractOkResult({ Ok: "success" }) // "success"
|
|
396
|
+
const result2 = extractOkResult({ ok: "success" }) // "success"
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Query Key Generation
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
const queryKey = reactor.generateQueryKey({
|
|
403
|
+
functionName: "get_user",
|
|
404
|
+
args: ["user-123"],
|
|
220
405
|
})
|
|
406
|
+
// ["canister-id", "get_user", "serialized-args"]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## TypeScript Types
|
|
221
410
|
|
|
222
|
-
|
|
223
|
-
const version = callMethod("version")
|
|
411
|
+
### Actor Types
|
|
224
412
|
|
|
225
|
-
|
|
413
|
+
```typescript
|
|
414
|
+
import type {
|
|
415
|
+
FunctionName, // Method names from actor service
|
|
416
|
+
ActorMethodParameters, // Parameter types for a method
|
|
417
|
+
ActorMethodReturnType, // Return type for a method
|
|
418
|
+
ReactorArgs, // Args with optional transforms
|
|
419
|
+
ReactorReturnOk, // Return type (Ok extracted from Result)
|
|
420
|
+
ReactorReturnErr, // Error type (Err from Result)
|
|
421
|
+
} from "@ic-reactor/core"
|
|
226
422
|
```
|
|
227
423
|
|
|
228
|
-
|
|
424
|
+
### State Types
|
|
229
425
|
|
|
230
426
|
```typescript
|
|
231
|
-
|
|
232
|
-
|
|
427
|
+
import type { AgentState, AuthState } from "@ic-reactor/core"
|
|
428
|
+
|
|
429
|
+
interface AgentState {
|
|
430
|
+
isInitialized: boolean
|
|
431
|
+
isInitializing: boolean
|
|
432
|
+
error: Error | undefined
|
|
433
|
+
network: "ic" | "local" | undefined
|
|
434
|
+
isLocalhost: boolean
|
|
435
|
+
}
|
|
233
436
|
|
|
234
|
-
|
|
437
|
+
interface AuthState {
|
|
438
|
+
identity: Identity | null
|
|
439
|
+
isAuthenticated: boolean
|
|
440
|
+
isAuthenticating: boolean
|
|
441
|
+
error: Error | undefined
|
|
442
|
+
}
|
|
235
443
|
```
|
|
236
444
|
|
|
237
|
-
|
|
445
|
+
## Advanced Usage
|
|
238
446
|
|
|
239
|
-
|
|
447
|
+
### Multiple Canisters
|
|
240
448
|
|
|
241
449
|
```typescript
|
|
242
|
-
|
|
243
|
-
import { createAgentManager } from "@ic-reactor/core"
|
|
450
|
+
const clientManager = new ClientManager({ queryClient, withProcessEnv: true })
|
|
244
451
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
452
|
+
// All reactors share the same agent and authentication
|
|
453
|
+
const backend = new Reactor<Backend>({
|
|
454
|
+
clientManager,
|
|
455
|
+
idlFactory: backendIdl,
|
|
456
|
+
canisterId: "...",
|
|
457
|
+
})
|
|
458
|
+
const ledger = new DisplayReactor<Ledger>({
|
|
459
|
+
clientManager,
|
|
460
|
+
idlFactory: ledgerIdl,
|
|
461
|
+
canisterId: "...",
|
|
248
462
|
})
|
|
463
|
+
const nft = new Reactor<NFT>({
|
|
464
|
+
clientManager,
|
|
465
|
+
idlFactory: nftIdl,
|
|
466
|
+
canisterId: "...",
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// Login once, all canisters use the same identity
|
|
470
|
+
await clientManager.login()
|
|
249
471
|
```
|
|
250
472
|
|
|
251
|
-
|
|
473
|
+
### Custom Polling Options
|
|
252
474
|
|
|
253
475
|
```typescript
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
476
|
+
const backend = new Reactor<_SERVICE>({
|
|
477
|
+
clientManager,
|
|
478
|
+
idlFactory,
|
|
479
|
+
canisterId: "...",
|
|
480
|
+
pollingOptions: {
|
|
481
|
+
maxRetries: 5,
|
|
482
|
+
strategyFactory: () => /* custom strategy */,
|
|
483
|
+
},
|
|
259
484
|
})
|
|
260
485
|
```
|
|
261
486
|
|
|
262
|
-
###
|
|
263
|
-
|
|
264
|
-
You can use Actor Managers to create your implementation of an actor. This allows you to manage the actor's lifecycle and state, as well as interact with the actor's methods.
|
|
487
|
+
### Direct Agent Access
|
|
265
488
|
|
|
266
489
|
```typescript
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
import { candid, canisterId, idlFactory } from "./declarations/candid"
|
|
270
|
-
import { agentManager } from "./agent"
|
|
490
|
+
// Get subnet ID
|
|
491
|
+
const subnetId = await backend.subnetId()
|
|
271
492
|
|
|
272
|
-
|
|
493
|
+
// Read subnet state
|
|
494
|
+
const state = await backend.subnetState({ paths: [...] })
|
|
273
495
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
canisterId,
|
|
277
|
-
idlFactory,
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
// Usage example
|
|
281
|
-
const data = await candidActor.callMethod("version")
|
|
282
|
-
console.log(data)
|
|
496
|
+
// Access underlying agent
|
|
497
|
+
const agent = backend.agent
|
|
283
498
|
```
|
|
499
|
+
|
|
500
|
+
## Documentation
|
|
501
|
+
|
|
502
|
+
For comprehensive guides and API reference, visit the [documentation site](https://b3pay.github.io/ic-reactor/v3).
|
|
503
|
+
|
|
504
|
+
## License
|
|
505
|
+
|
|
506
|
+
MIT © [Behrad Deylami](https://github.com/b3hr4d)
|