@ic-reactor/core 0.5.2 → 1.0.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 CHANGED
@@ -1,22 +1,18 @@
1
- # IC-ReActor - Core
1
+ # @ic-reactor/core
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.
3
+ 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.
4
4
 
5
- ## Features
5
+ ## Installation
6
6
 
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.
7
+ To get started with `@ic-reactor/core`, you can install the package using npm or Yarn:
12
8
 
13
- ## Installation
9
+ **Using npm:**
14
10
 
15
11
  ```bash
16
12
  npm install @ic-reactor/core
17
13
  ```
18
14
 
19
- or
15
+ **Using Yarn:**
20
16
 
21
17
  ```bash
22
18
  yarn add @ic-reactor/core
@@ -24,85 +20,191 @@ yarn add @ic-reactor/core
24
20
 
25
21
  ## Usage
26
22
 
27
- To get started with ReActor, you'll need to initialize it with your actor configurations. Here's a basic example:
23
+ `@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
24
 
29
- ```javascript
30
- import { createReActor } from "@ic-reactor/core"
25
+ ### Automatic Agent Creation
31
26
 
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
- ```
27
+ For ease of use, the `createReActorStore` factory function automatically sets up a new ReActor store instance, managing the agent and its state internally.
40
28
 
41
- ### Querying Data
29
+ **Example:**
42
30
 
43
- ```javascript
44
- const { recall, subscribe, getState, initialData, intervalId } = queryCall({
45
- functionName: "yourFunctionName",
46
- args: ["arg1", "arg2"],
47
- autoRefresh: true,
48
- refreshInterval: 3000,
31
+ ```typescript
32
+ import { createReActorStore } from "@ic-reactor/core"
33
+ import { candid, canisterId, idlFactory } from "./candid"
34
+
35
+ type Candid = typeof candid
36
+
37
+ const { callMethod, authenticate } = createReActor<Candid>({
38
+ canisterId,
39
+ idlFactory,
49
40
  })
50
41
 
51
- // Subscribe to changes
52
- const unsubscribe = subscribe((newState) => {
53
- // Handle new state
42
+ // Usage example
43
+ const identity = await authenticate()
44
+ const data = await callMethod("version")
45
+ console.log(data)
46
+ ```
47
+
48
+ ### Manual Agent Creation
49
+
50
+ If you require more control over the agent's lifecycle or configuration, `@ic-reactor/core` provides the `createAgentManager` function for manual agent instantiation.
51
+
52
+ **IC Agent Example:**
53
+
54
+ ```typescript
55
+ // agent.ts
56
+ import { createAgentManager } from "@ic-reactor/core"
57
+
58
+ export const agentManager = createAgentManager() // Connects to IC network by default
59
+
60
+ // Usage example
61
+ await agentManager.authenticate()
62
+ // Then use the store to access the authClient, identity, and more...
63
+ const { authClient, identity, authenticating } =
64
+ agentManager.authStore.getState()
65
+ ```
66
+
67
+ **Local Agent Example:**
68
+
69
+ For development purposes, you might want to connect to a local instance of the IC network:
70
+
71
+ ```typescript
72
+ // agent.ts
73
+ import { createAgentManager } from "@ic-reactor/core"
74
+
75
+ export const agentManager = createAgentManager({
76
+ isLocalEnv: true,
77
+ port: 8000, // Default port is 4943
54
78
  })
79
+ ```
55
80
 
56
- // Fetch data
57
- recall().then((data) => {
58
- // Handle initial data
81
+ Alternatively, you can specify a host directly:
82
+
83
+ ```typescript
84
+ export const agentManager = createAgentManager({
85
+ host: "http://localhost:8000",
59
86
  })
87
+ ```
88
+
89
+ ### Creating an Actor Manager
90
+
91
+ Once you have an agent manager, use `createActorManager` to instantiate an actor manager for calling methods on your canisters.
92
+
93
+ ```typescript
94
+ // actor.ts
95
+ import { createActorManager } from "@ic-reactor/core"
96
+ import { candid, canisterId, idlFactory } from "./candid"
97
+ import { agentManager } from "./agent"
98
+
99
+ type Candid = typeof candid
60
100
 
61
- // Get initial data
62
- initialData.then((data) => {
63
- // Handle initial data
101
+ const candidActor = createActorManager<Candid>({
102
+ agentManager,
103
+ canisterId,
104
+ idlFactory,
64
105
  })
65
106
 
66
- // Clear interval if autoRefresh is enabled
67
- if (intervalId) {
68
- clearInterval(intervalId)
69
- }
107
+ // Usage example
108
+ const data = await candidActor.callMethod("version")
109
+ console.log(data)
70
110
  ```
71
111
 
72
- ### Updating Data
112
+ ### Managing Multiple Actors
113
+
114
+ 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:
115
+
116
+ **Creating Actor Managers:**
117
+
118
+ First, ensure you have your actor managers set up for each canister:
119
+
120
+ ```typescript
121
+ // Assuming you've already set up `candidA`, `candidB`, `canisterIdA`, `canisterIdB`, and `agentManager`
122
+
123
+ import { createActorManager } from "@ic-reactor/core"
124
+ import { candidA, canisterIdA } from "./candidA"
125
+ import { candidB, canisterIdB } from "./candidB"
126
+ import { agentManager } from "./agent"
73
127
 
74
- ```javascript
75
- const { call, getState, subscribe } = updateCall({
76
- functionName: "yourUpdateFunction",
77
- args: ["arg1", "arg2"],
128
+ type CandidA = typeof candidA
129
+ type CandidB = typeof candidB
130
+
131
+ const actorA = createActorManager<CandidA>({
132
+ agentManager,
133
+ canisterId: canisterIdA,
134
+ idlFactory: candidA.idlFactory,
78
135
  })
79
136
 
80
- call().then((result) => {
81
- // Handle result
137
+ const actorB = createActorManager<CandidB>({
138
+ agentManager,
139
+ canisterId: canisterIdB,
140
+ idlFactory: candidB.idlFactory,
82
141
  })
142
+ ```
83
143
 
84
- // Get state
85
- const state = getState()
144
+ ### Using `callMethod` with Multiple Arguments
86
145
 
87
- // Subscribe to changes
88
- const unsubscribe = subscribe((newState) => {
89
- // Handle new state
90
- })
146
+ To call a method on a canister that requires multiple arguments, pass the method name followed by the arguments as separate parameters to `callMethod`:
147
+
148
+ ```typescript
149
+ // Example usage with CanisterA calling a method that requires one argument
150
+ const responseA = await actorA.callMethod("otherMethod", "arg1")
151
+ console.log("Response from CanisterA method:", responseA)
152
+
153
+ // Example usage with CanisterB calling a different method also with two arguments
154
+ const responseB = await actorB.callMethod("anotherMethod", "arg1", "arg2")
155
+ console.log("Response from CanisterB method:", responseB)
91
156
  ```
92
157
 
93
- ## API Reference
158
+ ### Using Candid Adapter
159
+
160
+ 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.
161
+ If both methods fail, it throws an error.
94
162
 
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.
163
+ ```typescript
164
+ import { createCandidAdapter } from "@ic-reactor/core"
165
+ import { agentManager } from "./agent"
99
166
 
100
- For more detailed API usage and options, please refer to the [documentation](#).
167
+ const candidAdapter = createCandidAdapter({ agentManager })
101
168
 
102
- ## Contributing
169
+ const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai"
103
170
 
104
- Contributions are welcome! Please read our [contributing guidelines](#) to get started.
171
+ // Usage example
172
+ try {
173
+ const definition = await candidAdapter.getCandidDefinition(canisterId)
174
+ console.log(definition)
175
+ } catch (error) {
176
+ console.error(error)
177
+ }
178
+ ```
179
+
180
+ ### Using `createReActorStore` with `CandidAdapter`
181
+
182
+ You can use the `candidAdapter` to fetch the Candid definition and then pass it to the `createReActorStore` function.
183
+
184
+ ```typescript
185
+ import { createReActorStore, createCandidAdapter } from "@ic-reactor/core"
186
+ import { agentManager } from "./agent"
187
+
188
+ const candidAdapter = createCandidAdapter({ agentManager })
189
+
190
+ const canisterId = "ryjl3-tyaaa-aaaaa-aaaba-cai" // NNS ICP Ledger Canister
191
+
192
+ // Usage example
193
+ try {
194
+ const { idlFactory } = await candidAdapter.getCandidDefinition(canisterId)
195
+ const { callMethod } = createReActorStore({
196
+ agentManager,
197
+ canisterId,
198
+ idlFactory,
199
+ })
200
+
201
+ const name = await callMethod("name")
202
+ console.log(name) // { name: 'Internet Computer' }
203
+ } catch (error) {
204
+ console.error(error)
205
+ }
206
+ ```
105
207
 
106
- ## License
208
+ ## Conclusion
107
209
 
108
- This project is licensed under [MIT License](LICENSE).
210
+ The `@ic-reactor/core` package offers a flexible and powerful way to interact with the Internet Computer, catering to both straightforward use cases with automatic agent management and more complex scenarios requiring manual control. By abstracting away some of the intricacies of agent and actor management, it enables developers to focus more on building their applications.
@@ -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, UpdateAgentOptions } from "../agent";
4
+ export * 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,157 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
+ return new (P || (P = Promise))(function (resolve, reject) {
19
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23
+ });
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ActorManager = void 0;
27
+ /* eslint-disable no-console */
28
+ const agent_1 = require("@dfinity/agent");
29
+ const helper_1 = require("../tools/helper");
30
+ const candid_1 = require("@dfinity/candid");
31
+ __exportStar(require("./types"), exports);
32
+ class ActorManager {
33
+ constructor(actorConfig) {
34
+ this._actor = null;
35
+ this.initialState = {
36
+ methodState: {},
37
+ initializing: false,
38
+ initialized: false,
39
+ error: undefined,
40
+ };
41
+ this.updateState = (newState) => {
42
+ this.actorStore.setState((state) => (Object.assign(Object.assign({}, state), newState)));
43
+ };
44
+ this.updateMethodState = (method, hash, newState) => {
45
+ this.actorStore.setState((state) => {
46
+ const methodState = state.methodState[method] || {};
47
+ const currentMethodState = methodState[hash] || {
48
+ loading: false,
49
+ data: undefined,
50
+ error: undefined,
51
+ };
52
+ const updatedMethodState = Object.assign(Object.assign({}, methodState), { [hash]: Object.assign(Object.assign({}, currentMethodState), newState) });
53
+ return Object.assign(Object.assign({}, state), { methodState: Object.assign(Object.assign({}, state.methodState), { [method]: updatedMethodState }) });
54
+ });
55
+ };
56
+ this.initialize = (options) => __awaiter(this, void 0, void 0, function* () {
57
+ yield this._agentManager.updateAgent(options);
58
+ });
59
+ this.initializeActor = (agent) => {
60
+ console.info(`Initializing actor ${this.canisterId} on ${agent.isLocal() ? "local" : "ic"} network`);
61
+ const { _idlFactory: idlFactory, canisterId } = this;
62
+ this.updateState({
63
+ initializing: true,
64
+ initialized: false,
65
+ methodState: {},
66
+ });
67
+ try {
68
+ if (!agent) {
69
+ throw new Error("Agent not initialized");
70
+ }
71
+ this._actor = agent_1.Actor.createActor(idlFactory, {
72
+ agent,
73
+ canisterId,
74
+ });
75
+ if (!this._actor) {
76
+ throw new Error("Failed to initialize actor");
77
+ }
78
+ this.updateState({
79
+ initializing: false,
80
+ initialized: true,
81
+ });
82
+ }
83
+ catch (error) {
84
+ console.error("Error in initializeActor:", error);
85
+ this.updateState({ error: error, initializing: false });
86
+ }
87
+ };
88
+ this.callMethod = (functionName, ...args) => __awaiter(this, void 0, void 0, function* () {
89
+ if (!this._actor) {
90
+ throw new Error("Actor not initialized");
91
+ }
92
+ if (!this._actor[functionName] ||
93
+ typeof this._actor[functionName] !== "function") {
94
+ throw new Error(`Method ${String(functionName)} not found`);
95
+ }
96
+ const method = this._actor[functionName];
97
+ const data = yield method(...args);
98
+ return data;
99
+ });
100
+ this.getAgent = () => {
101
+ return this._agentManager.getAgent();
102
+ };
103
+ // actor store
104
+ this.getActor = () => {
105
+ return this._actor;
106
+ };
107
+ this.getState = () => {
108
+ return this.actorStore.getState();
109
+ };
110
+ this.subscribeActorState = (listener) => {
111
+ return this.actorStore.subscribe(listener);
112
+ };
113
+ this.setState = (updater) => {
114
+ return this.actorStore.setState(updater);
115
+ };
116
+ const { agentManager, canisterId, idlFactory, withVisitor = false, withDevtools = false, initializeOnCreate = true, } = actorConfig;
117
+ this._agentManager = agentManager;
118
+ this.unsubscribeActor = this._agentManager.subscribeAgent(this.initializeActor);
119
+ this.canisterId = canisterId;
120
+ this._idlFactory = idlFactory;
121
+ if (withVisitor) {
122
+ this.visitFunction = this.extractService();
123
+ }
124
+ else {
125
+ this.visitFunction = emptyVisitor;
126
+ }
127
+ // Initialize stores
128
+ this.actorStore = (0, helper_1.createStoreWithOptionalDevtools)(this.initialState, {
129
+ withDevtools,
130
+ store: `actor-${String(canisterId)}`,
131
+ });
132
+ if (initializeOnCreate) {
133
+ this.initializeActor(agentManager.getAgent());
134
+ }
135
+ }
136
+ extractService() {
137
+ return this._idlFactory({ IDL: candid_1.IDL })._fields.reduce((acc, service) => {
138
+ const functionName = service[0];
139
+ const type = service[1];
140
+ const visit = ((extractorClass, data) => {
141
+ return type.accept(extractorClass, data || functionName);
142
+ });
143
+ acc[functionName] = visit;
144
+ return acc;
145
+ }, {});
146
+ }
147
+ // agent store
148
+ get agentManager() {
149
+ return this._agentManager;
150
+ }
151
+ }
152
+ exports.ActorManager = ActorManager;
153
+ const emptyVisitor = new Proxy({}, {
154
+ get: function (_, prop) {
155
+ 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.`);
156
+ },
157
+ });
@@ -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,32 @@
1
+ import { HttpAgent } from "@dfinity/agent";
2
+ import type { AgentStore, AgentManagerOptions, UpdateAgentOptions, AuthStore } from "./types";
3
+ export * from "./types";
4
+ export declare const IC_HOST_NETWORK_URI = "https://ic0.app";
5
+ export declare const LOCAL_HOST_NETWORK_URI = "http://127.0.0.1:4943";
6
+ export declare class AgentManager {
7
+ private _agent;
8
+ private _subscribers;
9
+ agentStore: AgentStore;
10
+ authStore: AuthStore;
11
+ isLocalEnv: boolean;
12
+ private initialAgentState;
13
+ private initialAuthState;
14
+ private updateAgentState;
15
+ private updateAuthState;
16
+ constructor(options?: AgentManagerOptions);
17
+ private initializeAgent;
18
+ subscribeAgent: (callback: (agent: HttpAgent) => void) => () => void;
19
+ unsubscribeAgent: (callback: (agent: HttpAgent) => void) => void;
20
+ private notifySubscribers;
21
+ updateAgent: (options?: UpdateAgentOptions) => Promise<void>;
22
+ authenticate: () => Promise<import("@dfinity/agent").Identity>;
23
+ getAgent: () => HttpAgent;
24
+ getAgentStore: () => AgentStore;
25
+ getAgentState: AgentStore["getState"];
26
+ subscribeAgentState: AgentStore["subscribe"];
27
+ getAuthState: AuthStore["getState"];
28
+ subscribeAuthState: AuthStore["subscribe"];
29
+ getAuthClient: () => import("@dfinity/auth-client").AuthClient | null;
30
+ getIdentity: () => import("@dfinity/agent").Identity | null;
31
+ getPrincipal: () => import("@dfinity/principal").Principal | null;
32
+ }