@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.
Files changed (123) hide show
  1. package/README.md +398 -175
  2. package/dist/client.d.ts +161 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +499 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/display/helper.d.ts +10 -0
  7. package/dist/display/helper.d.ts.map +1 -0
  8. package/dist/display/helper.js +67 -0
  9. package/dist/display/helper.js.map +1 -0
  10. package/dist/display/index.d.ts +4 -0
  11. package/dist/display/index.d.ts.map +1 -0
  12. package/dist/display/index.js +4 -0
  13. package/dist/display/index.js.map +1 -0
  14. package/dist/display/types.d.ts +31 -0
  15. package/dist/display/types.d.ts.map +1 -0
  16. package/dist/display/types.js +2 -0
  17. package/dist/display/types.js.map +1 -0
  18. package/dist/display/visitor.d.ts +28 -0
  19. package/dist/display/visitor.d.ts.map +1 -0
  20. package/dist/display/visitor.js +318 -0
  21. package/dist/display/visitor.js.map +1 -0
  22. package/dist/display-reactor.d.ts +245 -0
  23. package/dist/display-reactor.d.ts.map +1 -0
  24. package/dist/display-reactor.js +331 -0
  25. package/dist/display-reactor.js.map +1 -0
  26. package/dist/errors/index.d.ts +118 -0
  27. package/dist/errors/index.d.ts.map +1 -0
  28. package/dist/errors/index.js +204 -0
  29. package/dist/errors/index.js.map +1 -0
  30. package/dist/index.d.ts +9 -8
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +9 -47
  33. package/dist/index.js.map +1 -0
  34. package/dist/reactor.d.ts +133 -0
  35. package/dist/reactor.d.ts.map +1 -0
  36. package/dist/reactor.js +325 -0
  37. package/dist/reactor.js.map +1 -0
  38. package/dist/types/client.d.ts +89 -0
  39. package/dist/types/client.d.ts.map +1 -0
  40. package/dist/types/client.js +2 -0
  41. package/dist/types/client.js.map +1 -0
  42. package/dist/types/index.d.ts +6 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/dist/types/index.js +6 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/types/reactor.d.ts +117 -0
  47. package/dist/types/reactor.d.ts.map +1 -0
  48. package/dist/types/reactor.js +2 -0
  49. package/dist/types/reactor.js.map +1 -0
  50. package/dist/types/result.d.ts +48 -0
  51. package/dist/types/result.d.ts.map +1 -0
  52. package/dist/types/result.js +2 -0
  53. package/dist/types/result.js.map +1 -0
  54. package/dist/types/transform.d.ts +7 -0
  55. package/dist/types/transform.d.ts.map +1 -0
  56. package/dist/types/transform.js +2 -0
  57. package/dist/types/transform.js.map +1 -0
  58. package/dist/types/variant.d.ts +18 -0
  59. package/dist/types/variant.d.ts.map +1 -0
  60. package/dist/types/variant.js +2 -0
  61. package/dist/types/variant.js.map +1 -0
  62. package/dist/utils/agent.d.ts +30 -1
  63. package/dist/utils/agent.d.ts.map +1 -0
  64. package/dist/utils/agent.js +118 -16
  65. package/dist/utils/agent.js.map +1 -0
  66. package/dist/utils/candid.d.ts +39 -1
  67. package/dist/utils/candid.d.ts.map +1 -0
  68. package/dist/utils/candid.js +76 -16
  69. package/dist/utils/candid.js.map +1 -0
  70. package/dist/utils/constants.d.ts +3 -4
  71. package/dist/utils/constants.d.ts.map +1 -0
  72. package/dist/utils/constants.js +7 -11
  73. package/dist/utils/constants.js.map +1 -0
  74. package/dist/utils/helper.d.ts +16 -39
  75. package/dist/utils/helper.d.ts.map +1 -0
  76. package/dist/utils/helper.js +53 -155
  77. package/dist/utils/helper.js.map +1 -0
  78. package/dist/utils/index.d.ts +4 -5
  79. package/dist/utils/index.d.ts.map +1 -0
  80. package/dist/utils/index.js +5 -49
  81. package/dist/utils/index.js.map +1 -0
  82. package/dist/utils/polling.d.ts +176 -0
  83. package/dist/utils/polling.d.ts.map +1 -0
  84. package/dist/utils/polling.js +170 -0
  85. package/dist/utils/polling.js.map +1 -0
  86. package/dist/version.d.ts +5 -0
  87. package/dist/version.d.ts.map +1 -0
  88. package/dist/version.js +5 -0
  89. package/dist/version.js.map +1 -0
  90. package/package.json +65 -39
  91. package/LICENSE.md +0 -8
  92. package/dist/classes/actor/index.d.ts +0 -34
  93. package/dist/classes/actor/index.js +0 -245
  94. package/dist/classes/actor/types.d.ts +0 -113
  95. package/dist/classes/actor/types.js +0 -2
  96. package/dist/classes/adapter/index.d.ts +0 -19
  97. package/dist/classes/adapter/index.js +0 -140
  98. package/dist/classes/adapter/types.d.ts +0 -14
  99. package/dist/classes/adapter/types.js +0 -2
  100. package/dist/classes/agent/index.d.ts +0 -37
  101. package/dist/classes/agent/index.js +0 -221
  102. package/dist/classes/agent/types.d.ts +0 -87
  103. package/dist/classes/agent/types.js +0 -2
  104. package/dist/classes/index.d.ts +0 -3
  105. package/dist/classes/index.js +0 -19
  106. package/dist/classes/types.d.ts +0 -15
  107. package/dist/classes/types.js +0 -20
  108. package/dist/createActorManager.d.ts +0 -12
  109. package/dist/createActorManager.js +0 -17
  110. package/dist/createAgentManager.d.ts +0 -12
  111. package/dist/createAgentManager.js +0 -17
  112. package/dist/createCandidAdapter.d.ts +0 -11
  113. package/dist/createCandidAdapter.js +0 -16
  114. package/dist/createReactorCore.d.ts +0 -10
  115. package/dist/createReactorCore.js +0 -112
  116. package/dist/createReactorStore.d.ts +0 -11
  117. package/dist/createReactorStore.js +0 -31
  118. package/dist/types.d.ts +0 -96
  119. package/dist/types.js +0 -17
  120. package/dist/utils/hash.d.ts +0 -12
  121. package/dist/utils/hash.js +0 -70
  122. package/dist/utils/principal.d.ts +0 -1
  123. package/dist/utils/principal.js +0 -17
package/README.md CHANGED
@@ -1,283 +1,506 @@
1
- The `@ic-reactor/core` package provides a streamlined way to interact with the Internet Computer (IC). It simplifies agent and actor management, ensuring type-safe communication with canisters. This package offers utilities for creating and managing IC agents, enabling seamless interaction through a friendly API.
1
+ # @ic-reactor/core
2
2
 
3
- ## Installation
3
+ <div align="center">
4
+ <strong>The Core Library for Internet Computer Applications</strong>
5
+ <br><br>
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@ic-reactor/core.svg)](https://www.npmjs.com/package/@ic-reactor/core)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
10
+ </div>
4
11
 
5
- To get started with `@ic-reactor/core`, you can install the package using npm or Yarn:
12
+ ---
6
13
 
7
- **Using npm:**
14
+ Framework-agnostic core library for building type-safe Internet Computer applications with [TanStack Query](https://tanstack.com/query) integration.
8
15
 
9
- ```bash
10
- npm install @ic-reactor/core
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
- **Using Yarn:**
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
- yarn add @ic-reactor/core
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
- or you can use the UMD version:
37
+ ## Core Concepts
20
38
 
21
- ```html
22
- <script src="https://github.com/B3Pay/ic-reactor/releases/download/v2.0.0/ic-reactor-core.min.js"></script>
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
- ### Using `createReactorCore`
59
+ ## Quick Start
26
60
 
27
- For ease of use, the `createReactorCore` factory function automatically sets up a new Reactor instance, managing the agent and its state internally, and providing a simple API for authenticating, querying, and updating actors.
61
+ ### 1. Create ClientManager
28
62
 
29
- **Example:**
63
+ The `ClientManager` handles the IC agent, authentication, and query client:
30
64
 
31
65
  ```typescript
32
- import { createReactorCore } from "@ic-reactor/core"
33
- import { candid, canisterId, idlFactory } from "./declarations/candid"
66
+ import { ClientManager } from "@ic-reactor/core"
67
+ import { QueryClient } from "@tanstack/query-core"
34
68
 
35
- type Candid = typeof candid
69
+ const queryClient = new QueryClient({
70
+ defaultOptions: {
71
+ queries: { staleTime: 5 * 60 * 1000 }, // 5 minutes
72
+ },
73
+ })
36
74
 
37
- const { queryCall, updateCall, getPrincipal, login } =
38
- createReactorCore<Candid>({
39
- canisterId,
40
- idlFactory,
41
- withProcessEnv: true, // will use process.env.DFX_NETWORK
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
- You can find All available methods are returned from the `createReactorCore` function [here](https://b3pay.github.io/ic-reactor/interfaces/core.types.CreateReactorCoreReturnType.html).
84
+ ### 2. Create Reactor
85
+
86
+ The `Reactor` class wraps a canister with type-safe methods and caching:
46
87
 
47
88
  ```typescript
48
- // later in your code
49
- await login({
50
- onSuccess: () => {
51
- console.log("Logged in successfully")
52
- },
53
- onError: (error) => {
54
- console.error("Failed to login:", error)
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
- // queryCall, will automatically call and return a promise with the result
59
- const { dataPromise, call } = queryCall({
60
- functionName: "icrc1_balance_of",
61
- args: [{ owner: getPrincipal(), subaccount: [] }],
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
- console.log(await dataPromise)
65
-
66
- // updateCall
67
- const { call, subscribe } = updateCall({
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
- // subscribe to the update call
81
- subscribe(({ loading, error, data }) => {
82
- console.log({ loading, error, data })
113
+
114
+ // Get from cache (no network call)
115
+ const fromCache = backend.getQueryData({
116
+ functionName: "greet",
117
+ args: ["World"],
83
118
  })
84
119
 
85
- const result = await call()
86
- console.log(result)
120
+ // Invalidate cache
121
+ backend.invalidateQueries({ functionName: "greet" })
87
122
  ```
88
123
 
89
- ### Managing Multiple Actors
124
+ ## ClientManager API
125
+
126
+ ### Constructor Options
90
127
 
91
- When interacting with multiple canisters using `@ic-reactor/core`, you need one agent manager for each canister. This way, you can create separate reactor for each canister. This enables modular interaction with different services on the Internet Computer,
92
- and allows you to manage the state of each actor independently.
93
- Here's how to adjust the example to handle methods that require multiple arguments:
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
- Fist you need to create a agent manager:
139
+ ### Authentication Methods
96
140
 
97
141
  ```typescript
98
- // agent.ts
99
- import { createAgentManager } from "@ic-reactor/core"
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
- export const agentManager = createAgentManager() // Connects to IC network by default
155
+ // Manually authenticate (restore session)
156
+ const identity = await clientManager.authenticate()
102
157
  ```
103
158
 
104
- Then you can create a Actor for each canister:
159
+ ### State Subscriptions
105
160
 
106
161
  ```typescript
107
- // Assuming you've already set up `candidA`, `candidB`, and `agentManager`
108
- import { createActorManager } from "@ic-reactor/core"
109
- import * as candidA from "./declarations/candidA"
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
- const actorB = createActorManager<CandidB>({
123
- agentManager,
124
- canisterId: candidB.canisterId,
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
- You can now use the `actorA` and `actorB` instances to interact with their respective canisters:
183
+ ### Properties
130
184
 
131
185
  ```typescript
132
- const { dataPromise: version } = actorA.queryCall({
133
- functionName: "version",
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
- const { dataPromise: balance } = actorB.queryCall({
138
- functionName: "balance",
139
- args: [principal, []],
218
+ // Fetch and cache data
219
+ const data = await reactor.fetchQuery({
220
+ functionName: "get_data",
221
+ args: [],
140
222
  })
141
- console.log("Response from CanisterB method:", await balance)
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
- ### Using Candid Adapter
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
- The `CandidAdapter` class is used to interact with a canister and retrieve its Candid interface definition. It provides methods to fetch the Candid definition either from the canister's metadata or by using a temporary hack method.
147
- If both methods fail, it throws an error.
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 { createCandidAdapter } from "@ic-reactor/core"
151
- import { agentManager } from "./agent"
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
- const candidAdapter = createCandidAdapter({ agentManager })
284
+ `DisplayReactor` supports validators for mutation arguments:
154
285
 
155
- const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai"
286
+ ```typescript
287
+ import { DisplayReactor, ValidationError } from "@ic-reactor/core"
156
288
 
157
- // Usage example
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
- const definition = await candidAdapter.getCandidDefinition(canisterId)
160
- console.log(definition)
318
+ await backend.callMethodWithValidation({
319
+ functionName: "transfer",
320
+ args: [{ to: "", amount: "0" }],
321
+ })
161
322
  } catch (error) {
162
- console.error(error)
323
+ if (error instanceof ValidationError) {
324
+ console.log(error.issues)
325
+ }
163
326
  }
164
327
  ```
165
328
 
166
- ### Using `createReactorCore` with `CandidAdapter`
329
+ ## Error Handling
167
330
 
168
- You can use the `candidAdapter` to fetch the Candid definition and then pass it to the `createReactorCore` function.
331
+ ### Error Types
169
332
 
170
333
  ```typescript
171
- import { createReactorCore, createCandidAdapter } from "@ic-reactor/core"
172
- import { agentManager } from "./agent"
334
+ import {
335
+ CallError,
336
+ CanisterError,
337
+ ValidationError,
338
+ isCallError,
339
+ isCanisterError,
340
+ isValidationError,
341
+ } from "@ic-reactor/core"
342
+ ```
173
343
 
174
- const candidAdapter = createCandidAdapter({ agentManager })
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
- const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai" // NNS ICP Ledger Canister
350
+ ### Handling Errors
177
351
 
178
- // Usage example
352
+ ```typescript
179
353
  try {
180
- const { idlFactory } = await candidAdapter.getCandidDefinition(canisterId)
181
- const { callMethod } = createReactorCore({
182
- agentManager,
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
- console.error(error)
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
- ### Using store to lower level control
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
- import { createReactorStore } from "@ic-reactor/core"
200
- import { candid, canisterId, idlFactory } from "./declarations/candid"
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
- type Candid = typeof candid
384
+ ## Utilities
203
385
 
204
- const { agentManager, callMethod } = createReactorStore<Candid>({
205
- canisterId,
206
- idlFactory,
207
- })
386
+ ### Result Unwrapping
208
387
 
209
- // Usage example
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
- authClient?.login({
214
- onSuccess: () => {
215
- console.log("Logged in successfully")
216
- },
217
- onError: (error) => {
218
- console.error("Failed to login:", error)
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
- // Call a method
223
- const version = callMethod("version")
411
+ ### Actor Types
224
412
 
225
- console.log("Response from version method:", await version)
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
- **IC Agent Example:**
424
+ ### State Types
229
425
 
230
426
  ```typescript
231
- // agent.ts
232
- import { createAgentManager } from "@ic-reactor/core"
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
- export const agentManager = createAgentManager() // Connects to IC network by default
437
+ interface AuthState {
438
+ identity: Identity | null
439
+ isAuthenticated: boolean
440
+ isAuthenticating: boolean
441
+ error: Error | undefined
442
+ }
235
443
  ```
236
444
 
237
- **Local Agent Example:**
445
+ ## Advanced Usage
238
446
 
239
- For development purposes, you might want to connect to a local instance of the IC network:
447
+ ### Multiple Canisters
240
448
 
241
449
  ```typescript
242
- // agent.ts
243
- import { createAgentManager } from "@ic-reactor/core"
450
+ const clientManager = new ClientManager({ queryClient, withProcessEnv: true })
244
451
 
245
- export const agentManager = createAgentManager({
246
- withLocalEnv: true,
247
- port: 8000, // Default port is 4943
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
- Alternatively, you can specify a host directly:
473
+ ### Custom Polling Options
252
474
 
253
475
  ```typescript
254
- // agent.ts
255
- import { createAgentManager } from "@ic-reactor/core"
256
-
257
- export const agentManager = createAgentManager({
258
- host: "http://localhost:8000",
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
- ### Creating an Actor Manager
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
- // actor.ts
268
- import { createActorManager } from "@ic-reactor/core"
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
- type Candid = typeof candid
493
+ // Read subnet state
494
+ const state = await backend.subnetState({ paths: [...] })
273
495
 
274
- const candidActor = createActorManager<Candid>({
275
- agentManager,
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)