@ic-reactor/core 0.5.3 → 1.0.1

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 CHANGED
@@ -1,22 +1,16 @@
1
- # IC-ReActor - Core
1
+ The `@ic-reactor/core` package provides a streamlined way to interact with the Internet Computer (IC) by simplifying agent and actor management. It offers utilities for creating and managing IC agents, enabling seamless communication with canisters through a friendly API.
2
2
 
3
- ReActor is a JavaScript utility designed to streamline state management and interaction with actors in the Internet Computer (IC) blockchain environment. It provides a simple and effective way to handle asynchronous calls, state updates, and subscriptions, making it easier to build responsive and data-driven applications.
4
-
5
- ## Features
3
+ ## Installation
6
4
 
7
- - **State Management**: Efficiently manage the state of your application with actor-based interactions.
8
- - **Asynchronous Handling**: Simplify asynchronous calls and data handling with built-in methods.
9
- - **Subscription Mechanism**: Subscribe to state changes and update your UI in real-time.
10
- - **Auto-Refresh Capability**: Automatically refresh data at specified intervals.
11
- - **Customizable**: Easily adaptable to various use cases within the IC ecosystem.
5
+ To get started with `@ic-reactor/core`, you can install the package using npm or Yarn:
12
6
 
13
- ## Installation
7
+ **Using npm:**
14
8
 
15
9
  ```bash
16
10
  npm install @ic-reactor/core
17
11
  ```
18
12
 
19
- or
13
+ **Using Yarn:**
20
14
 
21
15
  ```bash
22
16
  yarn add @ic-reactor/core
@@ -24,85 +18,187 @@ yarn add @ic-reactor/core
24
18
 
25
19
  ## Usage
26
20
 
27
- To get started with ReActor, you'll need to initialize it with your actor configurations. Here's a basic example:
21
+ `@ic-reactor/core` can be utilized in two primary ways: automatic agent creation and manual agent management. Below are examples of both approaches to suit your project's needs.
28
22
 
29
- ```javascript
30
- import { createReActor } from "@ic-reactor/core"
23
+ ### Automatic Agent Creation
31
24
 
32
- const { actorStore, authStore, queryCall, updateCall } = createReActor(
33
- (agent) => createActor("bd3sg-teaaa-aaaaa-qaaba-cai", { agent }),
34
- {
35
- host: "https://localhost:4943",
36
- initializeOnMount: true,
37
- }
38
- )
39
- ```
25
+ For ease of use, the `createReActorStore` factory function automatically sets up a new ReActor store instance, managing the agent and its state internally.
26
+
27
+ **Example:**
28
+
29
+ ```typescript
30
+ import { createReActorStore } from "@ic-reactor/core"
31
+ import { candid, canisterId, idlFactory } from "./candid"
40
32
 
41
- ### Querying Data
33
+ type Candid = typeof candid
42
34
 
43
- ```javascript
44
- const { recall, subscribe, getState, initialData, intervalId } = queryCall({
45
- functionName: "yourFunctionName",
46
- args: ["arg1", "arg2"],
47
- autoRefresh: true,
48
- refreshInterval: 3000,
35
+ const { callMethod, authenticate } = createReActor<Candid>({
36
+ canisterId,
37
+ idlFactory,
49
38
  })
50
39
 
51
- // Subscribe to changes
52
- const unsubscribe = subscribe((newState) => {
53
- // Handle new state
40
+ // Usage example
41
+ const identity = await authenticate()
42
+ const data = await callMethod("version")
43
+ console.log(data)
44
+ ```
45
+
46
+ ### Manual Agent Creation
47
+
48
+ If you require more control over the agent's lifecycle or configuration, `@ic-reactor/core` provides the `createAgentManager` function for manual agent instantiation.
49
+
50
+ **IC Agent Example:**
51
+
52
+ ```typescript
53
+ // agent.ts
54
+ import { createAgentManager } from "@ic-reactor/core"
55
+
56
+ export const agentManager = createAgentManager() // Connects to IC network by default
57
+
58
+ // Usage example
59
+ await agentManager.authenticate()
60
+ // Then use the store to access the authClient, identity, and more...
61
+ const { authClient, identity, authenticating } =
62
+ agentManager.authStore.getState()
63
+ ```
64
+
65
+ **Local Agent Example:**
66
+
67
+ For development purposes, you might want to connect to a local instance of the IC network:
68
+
69
+ ```typescript
70
+ // agent.ts
71
+ import { createAgentManager } from "@ic-reactor/core"
72
+
73
+ export const agentManager = createAgentManager({
74
+ isLocalEnv: true,
75
+ port: 8000, // Default port is 4943
54
76
  })
77
+ ```
78
+
79
+ Alternatively, you can specify a host directly:
55
80
 
56
- // Fetch data
57
- recall().then((data) => {
58
- // Handle initial data
81
+ ```typescript
82
+ export const agentManager = createAgentManager({
83
+ host: "http://localhost:8000",
59
84
  })
85
+ ```
86
+
87
+ ### Creating an Actor Manager
88
+
89
+ Once you have an agent manager, use `createActorManager` to instantiate an actor manager for calling methods on your canisters.
60
90
 
61
- // Get initial data
62
- initialData.then((data) => {
63
- // Handle initial data
91
+ ```typescript
92
+ // actor.ts
93
+ import { createActorManager } from "@ic-reactor/core"
94
+ import { candid, canisterId, idlFactory } from "./candid"
95
+ import { agentManager } from "./agent"
96
+
97
+ type Candid = typeof candid
98
+
99
+ const candidActor = createActorManager<Candid>({
100
+ agentManager,
101
+ canisterId,
102
+ idlFactory,
64
103
  })
65
104
 
66
- // Clear interval if autoRefresh is enabled
67
- if (intervalId) {
68
- clearInterval(intervalId)
69
- }
105
+ // Usage example
106
+ const data = await candidActor.callMethod("version")
107
+ console.log(data)
70
108
  ```
71
109
 
72
- ### Updating Data
110
+ ### Managing Multiple Actors
111
+
112
+ When interacting with multiple canisters using `@ic-reactor/core`, you can create separate actor managers for each canister. This enables modular interaction with different services on the Internet Computer. Here's how to adjust the example to handle methods that require multiple arguments:
113
+
114
+ **Creating Actor Managers:**
115
+
116
+ First, ensure you have your actor managers set up for each canister:
73
117
 
74
- ```javascript
75
- const { call, getState, subscribe } = updateCall({
76
- functionName: "yourUpdateFunction",
77
- args: ["arg1", "arg2"],
118
+ ```typescript
119
+ // Assuming you've already set up `candidA`, `candidB`, `canisterIdA`, `canisterIdB`, and `agentManager`
120
+
121
+ import { createActorManager } from "@ic-reactor/core"
122
+ import { candidA, canisterIdA } from "./candidA"
123
+ import { candidB, canisterIdB } from "./candidB"
124
+ import { agentManager } from "./agent"
125
+
126
+ type CandidA = typeof candidA
127
+ type CandidB = typeof candidB
128
+
129
+ const actorA = createActorManager<CandidA>({
130
+ agentManager,
131
+ canisterId: canisterIdA,
132
+ idlFactory: candidA.idlFactory,
78
133
  })
79
134
 
80
- call().then((result) => {
81
- // Handle result
135
+ const actorB = createActorManager<CandidB>({
136
+ agentManager,
137
+ canisterId: canisterIdB,
138
+ idlFactory: candidB.idlFactory,
82
139
  })
140
+ ```
83
141
 
84
- // Get state
85
- const state = getState()
142
+ ### Using `callMethod` with Multiple Arguments
86
143
 
87
- // Subscribe to changes
88
- const unsubscribe = subscribe((newState) => {
89
- // Handle new state
90
- })
144
+ To call a method on a canister that requires multiple arguments, pass the method name followed by the arguments as separate parameters to `callMethod`:
145
+
146
+ ```typescript
147
+ // Example usage with CanisterA calling a method that requires one argument
148
+ const responseA = await actorA.callMethod("otherMethod", "arg1")
149
+ console.log("Response from CanisterA method:", responseA)
150
+
151
+ // Example usage with CanisterB calling a different method also with two arguments
152
+ const responseB = await actorB.callMethod("anotherMethod", "arg1", "arg2")
153
+ console.log("Response from CanisterB method:", responseB)
91
154
  ```
92
155
 
93
- ## API Reference
156
+ ### Using Candid Adapter
157
+
158
+ 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.
159
+ If both methods fail, it throws an error.
160
+
161
+ ```typescript
162
+ import { createCandidAdapter } from "@ic-reactor/core"
163
+ import { agentManager } from "./agent"
94
164
 
95
- - `queryCall`: Fetches and subscribes to data from an actor method. Returns an object containing methods for recalling data, subscribing to changes, getting the current state, and the initial data promise.
96
- - `updateCall`: Updates data and handles state changes for an actor method. Returns an object containing methods for calling the update function, subscribing to changes, and getting the current state.
97
- - `getState`: Retrieves the current state based on the request hash.
98
- - `subscribe`: Allows subscription to state changes.
165
+ const candidAdapter = createCandidAdapter({ agentManager })
99
166
 
100
- For more detailed API usage and options, please refer to the [documentation](#).
167
+ const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai"
168
+
169
+ // Usage example
170
+ try {
171
+ const definition = await candidAdapter.getCandidDefinition(canisterId)
172
+ console.log(definition)
173
+ } catch (error) {
174
+ console.error(error)
175
+ }
176
+ ```
101
177
 
102
- ## Contributing
178
+ ### Using `createReActorStore` with `CandidAdapter`
103
179
 
104
- Contributions are welcome! Please read our [contributing guidelines](#) to get started.
180
+ You can use the `candidAdapter` to fetch the Candid definition and then pass it to the `createReActorStore` function.
105
181
 
106
- ## License
182
+ ```typescript
183
+ import { createReActorStore, createCandidAdapter } from "@ic-reactor/core"
184
+ import { agentManager } from "./agent"
107
185
 
108
- This project is licensed under [MIT License](LICENSE).
186
+ const candidAdapter = createCandidAdapter({ agentManager })
187
+
188
+ const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai" // NNS ICP Ledger Canister
189
+
190
+ // Usage example
191
+ try {
192
+ const { idlFactory } = await candidAdapter.getCandidDefinition(canisterId)
193
+ const { callMethod } = createReActorStore({
194
+ agentManager,
195
+ canisterId,
196
+ idlFactory,
197
+ })
198
+
199
+ const name = await callMethod("name")
200
+ console.log(name) // { name: 'Internet Computer' }
201
+ } catch (error) {
202
+ console.error(error)
203
+ }
204
+ ```
@@ -0,0 +1,31 @@
1
+ import type { HttpAgent } from "@dfinity/agent";
2
+ import type { CanisterId, ActorMethodArgs, ActorMethodReturnType, ActorStore, ActorManagerOptions, FunctionName, VisitService, BaseActor, ActorMethodState } from "./types";
3
+ import type { AgentManager } from "../agent";
4
+ import type { UpdateAgentOptions } from "../types";
5
+ export declare class ActorManager<A = BaseActor> {
6
+ private _actor;
7
+ private _idlFactory;
8
+ private _agentManager;
9
+ canisterId: CanisterId;
10
+ actorStore: ActorStore<A>;
11
+ visitFunction: VisitService<A>;
12
+ private initialState;
13
+ unsubscribeActor: () => void;
14
+ private updateState;
15
+ updateMethodState: (method: FunctionName<A>, hash: string, newState: Partial<{
16
+ data: ActorMethodReturnType<A[FunctionName<A>]> | undefined;
17
+ loading: boolean;
18
+ error: Error | undefined;
19
+ }>) => void;
20
+ constructor(actorConfig: ActorManagerOptions);
21
+ initialize: (options?: UpdateAgentOptions) => Promise<void>;
22
+ extractService(): VisitService<A>;
23
+ private initializeActor;
24
+ callMethod: <M extends FunctionName<A>>(functionName: M, ...args: ActorMethodArgs<A[M]>) => Promise<ActorMethodReturnType<A[M]>>;
25
+ get agentManager(): AgentManager;
26
+ getAgent: () => HttpAgent;
27
+ getActor: () => A | null;
28
+ getState: ActorStore<A>["getState"];
29
+ subscribeActorState: ActorStore<A>["subscribe"];
30
+ setState: ActorStore<A>["setState"];
31
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ActorManager = void 0;
13
+ /* eslint-disable no-console */
14
+ const agent_1 = require("@dfinity/agent");
15
+ const helper_1 = require("../tools/helper");
16
+ const candid_1 = require("@dfinity/candid");
17
+ class ActorManager {
18
+ constructor(actorConfig) {
19
+ this._actor = null;
20
+ this.initialState = {
21
+ methodState: {},
22
+ initializing: false,
23
+ initialized: false,
24
+ error: undefined,
25
+ };
26
+ this.updateState = (newState) => {
27
+ this.actorStore.setState((state) => (Object.assign(Object.assign({}, state), newState)));
28
+ };
29
+ this.updateMethodState = (method, hash, newState) => {
30
+ this.actorStore.setState((state) => {
31
+ const methodState = state.methodState[method] || {};
32
+ const currentMethodState = methodState[hash] || {
33
+ loading: false,
34
+ data: undefined,
35
+ error: undefined,
36
+ };
37
+ const updatedMethodState = Object.assign(Object.assign({}, methodState), { [hash]: Object.assign(Object.assign({}, currentMethodState), newState) });
38
+ return Object.assign(Object.assign({}, state), { methodState: Object.assign(Object.assign({}, state.methodState), { [method]: updatedMethodState }) });
39
+ });
40
+ };
41
+ this.initialize = (options) => __awaiter(this, void 0, void 0, function* () {
42
+ yield this._agentManager.updateAgent(options);
43
+ });
44
+ this.initializeActor = (agent) => {
45
+ console.info(`Initializing actor ${this.canisterId} on ${agent.isLocal() ? "local" : "ic"} network`);
46
+ const { _idlFactory: idlFactory, canisterId } = this;
47
+ this.updateState({
48
+ initializing: true,
49
+ initialized: false,
50
+ methodState: {},
51
+ });
52
+ try {
53
+ if (!agent) {
54
+ throw new Error("Agent not initialized");
55
+ }
56
+ this._actor = agent_1.Actor.createActor(idlFactory, {
57
+ agent,
58
+ canisterId,
59
+ });
60
+ if (!this._actor) {
61
+ throw new Error("Failed to initialize actor");
62
+ }
63
+ this.updateState({
64
+ initializing: false,
65
+ initialized: true,
66
+ });
67
+ }
68
+ catch (error) {
69
+ console.error("Error in initializeActor:", error);
70
+ this.updateState({ error: error, initializing: false });
71
+ }
72
+ };
73
+ this.callMethod = (functionName, ...args) => __awaiter(this, void 0, void 0, function* () {
74
+ if (!this._actor) {
75
+ throw new Error("Actor not initialized");
76
+ }
77
+ if (!this._actor[functionName] ||
78
+ typeof this._actor[functionName] !== "function") {
79
+ throw new Error(`Method ${String(functionName)} not found`);
80
+ }
81
+ const method = this._actor[functionName];
82
+ const data = yield method(...args);
83
+ return data;
84
+ });
85
+ this.getAgent = () => {
86
+ return this._agentManager.getAgent();
87
+ };
88
+ // actor store
89
+ this.getActor = () => {
90
+ return this._actor;
91
+ };
92
+ this.getState = () => {
93
+ return this.actorStore.getState();
94
+ };
95
+ this.subscribeActorState = (listener) => {
96
+ return this.actorStore.subscribe(listener);
97
+ };
98
+ this.setState = (updater) => {
99
+ return this.actorStore.setState(updater);
100
+ };
101
+ const { agentManager, canisterId, idlFactory, withVisitor = false, withDevtools = false, initializeOnCreate = true, } = actorConfig;
102
+ this._agentManager = agentManager;
103
+ this.unsubscribeActor = this._agentManager.subscribeAgent(this.initializeActor);
104
+ this.canisterId = canisterId;
105
+ this._idlFactory = idlFactory;
106
+ if (withVisitor) {
107
+ this.visitFunction = this.extractService();
108
+ }
109
+ else {
110
+ this.visitFunction = emptyVisitor;
111
+ }
112
+ // Initialize stores
113
+ this.actorStore = (0, helper_1.createStoreWithOptionalDevtools)(this.initialState, {
114
+ withDevtools,
115
+ store: `actor-${String(canisterId)}`,
116
+ });
117
+ if (initializeOnCreate) {
118
+ this.initializeActor(agentManager.getAgent());
119
+ }
120
+ }
121
+ extractService() {
122
+ return this._idlFactory({ IDL: candid_1.IDL })._fields.reduce((acc, service) => {
123
+ const functionName = service[0];
124
+ const type = service[1];
125
+ const visit = ((extractorClass, data) => {
126
+ return type.accept(extractorClass, data || functionName);
127
+ });
128
+ acc[functionName] = visit;
129
+ return acc;
130
+ }, {});
131
+ }
132
+ // agent store
133
+ get agentManager() {
134
+ return this._agentManager;
135
+ }
136
+ }
137
+ exports.ActorManager = ActorManager;
138
+ const emptyVisitor = new Proxy({}, {
139
+ get: function (_, prop) {
140
+ throw new Error(`Cannot visit function "${String(prop)}" without initializing the actor with the visitor option, please set the withVisitor option to true when creating the actor manager.`);
141
+ },
142
+ });
@@ -0,0 +1,46 @@
1
+ import type { IDL } from "@dfinity/candid";
2
+ import type { StoreApi } from "zustand";
3
+ import type { AgentManager } from "../agent";
4
+ import type { ActorMethod, ActorSubclass, Principal } from "../types";
5
+ export interface DefaultActorType {
6
+ [key: string]: ActorMethod;
7
+ }
8
+ export type BaseActor<T = DefaultActorType> = ActorSubclass<T>;
9
+ export type FunctionName<A = BaseActor> = keyof A & string;
10
+ export type FunctionType = "query" | "update";
11
+ export type CanisterId = string | Principal;
12
+ export interface ActorManagerOptions {
13
+ agentManager: AgentManager;
14
+ idlFactory: IDL.InterfaceFactory;
15
+ canisterId: CanisterId;
16
+ withVisitor?: boolean;
17
+ withDevtools?: boolean;
18
+ initializeOnCreate?: boolean;
19
+ }
20
+ export type VisitorType<V> = V extends IDL.Visitor<infer D, infer R> ? {
21
+ data: D;
22
+ returnType: R;
23
+ } : never;
24
+ export type VisitService<A = BaseActor, M extends FunctionName<A> = FunctionName<A>> = {
25
+ [K in M]: <V extends IDL.Visitor<unknown, unknown>>(extractorClass: V, data?: VisitorType<V>["data"]) => ReturnType<V["visitFunc"]>;
26
+ };
27
+ export type ActorMethodArgs<T> = T extends ActorMethod<infer Args, any> ? Args : never;
28
+ export type ActorMethodReturnType<T> = T extends ActorMethod<any, infer Ret> ? Ret : never;
29
+ export interface ActorMethodState<A, M extends keyof A> {
30
+ [key: string]: {
31
+ data: ActorMethodReturnType<A[M]> | undefined;
32
+ loading: boolean;
33
+ error: Error | undefined;
34
+ };
35
+ }
36
+ export type ActorMethodStates<A> = {
37
+ [M in keyof A]: ActorMethodState<A, M>;
38
+ };
39
+ export type ActorState<A> = {
40
+ initialized: boolean;
41
+ initializing: boolean;
42
+ error: Error | undefined;
43
+ methodState: ActorMethodStates<A>;
44
+ };
45
+ export type ActorStore<A = BaseActor> = StoreApi<ActorState<A>>;
46
+ export type CallActorMethod<A = BaseActor> = <M extends keyof A>(functionName: M, ...args: ActorMethodArgs<A[M]>) => Promise<ActorMethodReturnType<A[M]>>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,31 @@
1
+ import { HttpAgent } from "@dfinity/agent";
2
+ import type { AgentStore, AgentManagerOptions, UpdateAgentOptions, AuthStore } from "./types";
3
+ export declare const IC_HOST_NETWORK_URI = "https://ic0.app";
4
+ export declare const LOCAL_HOST_NETWORK_URI = "http://127.0.0.1:4943";
5
+ export declare class AgentManager {
6
+ private _agent;
7
+ private _subscribers;
8
+ agentStore: AgentStore;
9
+ authStore: AuthStore;
10
+ isLocalEnv: boolean;
11
+ private initialAgentState;
12
+ private initialAuthState;
13
+ private updateAgentState;
14
+ private updateAuthState;
15
+ constructor(options?: AgentManagerOptions);
16
+ private initializeAgent;
17
+ subscribeAgent: (callback: (agent: HttpAgent) => void) => () => void;
18
+ unsubscribeAgent: (callback: (agent: HttpAgent) => void) => void;
19
+ private notifySubscribers;
20
+ updateAgent: (options?: UpdateAgentOptions) => Promise<void>;
21
+ authenticate: () => Promise<import("@dfinity/agent").Identity>;
22
+ getAgent: () => HttpAgent;
23
+ getAgentStore: () => AgentStore;
24
+ getAgentState: AgentStore["getState"];
25
+ subscribeAgentState: AgentStore["subscribe"];
26
+ getAuthState: AuthStore["getState"];
27
+ subscribeAuthState: AuthStore["subscribe"];
28
+ getAuthClient: () => import("@dfinity/auth-client").AuthClient | null;
29
+ getIdentity: () => import("@dfinity/agent").Identity | null;
30
+ getPrincipal: () => import("@dfinity/principal").Principal | null;
31
+ }