@ic-reactor/vite-plugin 0.1.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 +323 -0
- package/dist/index.cjs +422 -0
- package/dist/index.d.cts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +384 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# @ic-reactor/vite-plugin
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<strong>Zero-config Vite plugin for auto-generating IC reactor hooks from Candid files.</strong>
|
|
5
|
+
<br><br>
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@ic-reactor/vite-plugin)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Automatically generate type-safe React hooks for your Internet Computer canisters. This plugin watches your `.did` files and generates ready-to-use hooks with full TypeScript support.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- ⚡ **Zero Config** — Point to your `.did` file and get hooks instantly
|
|
19
|
+
- 🔄 **Hot Reload** — Automatically regenerates hooks when `.did` files change
|
|
20
|
+
- 📦 **TypeScript Declarations** — Full type safety with auto-generated types
|
|
21
|
+
- 🎯 **Display Types** — Optional `DisplayReactor` support for React-friendly types
|
|
22
|
+
- 🔌 **Flexible** — Works with any `ClientManager` configuration
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# With npm
|
|
28
|
+
npm install -D @ic-reactor/vite-plugin
|
|
29
|
+
|
|
30
|
+
# With pnpm
|
|
31
|
+
pnpm add -D @ic-reactor/vite-plugin
|
|
32
|
+
|
|
33
|
+
# Required peer dependencies
|
|
34
|
+
npm install @ic-reactor/react @tanstack/react-query @icp-sdk/core
|
|
35
|
+
npm install -D @icp-sdk/bindgen
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### 1. Configure the Plugin
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// vite.config.ts
|
|
44
|
+
import { defineConfig } from "vite"
|
|
45
|
+
import react from "@vitejs/plugin-react"
|
|
46
|
+
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
47
|
+
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
plugins: [
|
|
50
|
+
react(),
|
|
51
|
+
icReactorPlugin({
|
|
52
|
+
canisters: [
|
|
53
|
+
{
|
|
54
|
+
name: "backend",
|
|
55
|
+
didFile: "./backend/backend.did",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Create Your ClientManager
|
|
64
|
+
|
|
65
|
+
The plugin expects you to have a `ClientManager` exported from a file. By default, it looks for `./src/lib/client.ts`:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// src/lib/client.ts
|
|
69
|
+
import { ClientManager } from "@ic-reactor/react"
|
|
70
|
+
import { QueryClient } from "@tanstack/react-query"
|
|
71
|
+
|
|
72
|
+
export const queryClient = new QueryClient()
|
|
73
|
+
|
|
74
|
+
export const clientManager = new ClientManager({
|
|
75
|
+
queryClient,
|
|
76
|
+
withCanisterEnv: true, // Enable environment-based canister ID resolution
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Use Generated Hooks
|
|
81
|
+
|
|
82
|
+
The plugin generates hooks in `./src/canisters/<name>/index.ts`:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// Generated at: ./src/canisters/backend/index.ts
|
|
86
|
+
import {
|
|
87
|
+
backendReactor,
|
|
88
|
+
useBackendQuery,
|
|
89
|
+
useBackendMutation,
|
|
90
|
+
useBackendSuspenseQuery,
|
|
91
|
+
} from "./canisters/backend"
|
|
92
|
+
|
|
93
|
+
function MyComponent() {
|
|
94
|
+
// Query data
|
|
95
|
+
const { data, isPending } = useBackendQuery({
|
|
96
|
+
functionName: "get_message",
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Mutate data
|
|
100
|
+
const { mutate } = useBackendMutation({
|
|
101
|
+
functionName: "set_message",
|
|
102
|
+
onSuccess: () => console.log("Message updated!"),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div>
|
|
107
|
+
<p>{isPending ? "Loading..." : data}</p>
|
|
108
|
+
<button onClick={() => mutate(["Hello IC!"])}>Update</button>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
### Plugin Options
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface IcReactorPluginOptions {
|
|
120
|
+
/** List of canisters to generate hooks for */
|
|
121
|
+
canisters: CanisterConfig[]
|
|
122
|
+
/** Base output directory (default: "./src/canisters") */
|
|
123
|
+
outDir?: string
|
|
124
|
+
/** Path to import ClientManager from (default: "../../lib/client") */
|
|
125
|
+
clientManagerPath?: string
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface CanisterConfig {
|
|
129
|
+
/** Name of the canister (used for variable naming) */
|
|
130
|
+
name: string
|
|
131
|
+
/** Path to the .did file */
|
|
132
|
+
didFile: string
|
|
133
|
+
/** Output directory (default: ./src/canisters/<name>) */
|
|
134
|
+
outDir?: string
|
|
135
|
+
/** Use DisplayReactor for React-friendly types (default: true) */
|
|
136
|
+
useDisplayReactor?: boolean
|
|
137
|
+
/** Path to import ClientManager from (relative to generated file) */
|
|
138
|
+
clientManagerPath?: string
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Example: Multiple Canisters
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// vite.config.ts
|
|
146
|
+
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
147
|
+
|
|
148
|
+
export default defineConfig({
|
|
149
|
+
plugins: [
|
|
150
|
+
icReactorPlugin({
|
|
151
|
+
clientManagerPath: "@/lib/client", // Global default
|
|
152
|
+
canisters: [
|
|
153
|
+
{
|
|
154
|
+
name: "backend",
|
|
155
|
+
didFile: "./backend/backend.did",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "ledger",
|
|
159
|
+
didFile: "./ledger/ledger.did",
|
|
160
|
+
useDisplayReactor: true, // BigInt → string, etc.
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "nft",
|
|
164
|
+
didFile: "./nft/nft.did",
|
|
165
|
+
outDir: "./src/services/nft", // Custom output
|
|
166
|
+
clientManagerPath: "@/lib/nft-client", // Custom client
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Advanced Plugin
|
|
175
|
+
|
|
176
|
+
For more granular control, use `icReactorAdvancedPlugin` which generates individual hooks per method:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { icReactorAdvancedPlugin } from "@ic-reactor/vite-plugin"
|
|
180
|
+
|
|
181
|
+
export default defineConfig({
|
|
182
|
+
plugins: [
|
|
183
|
+
icReactorAdvancedPlugin({
|
|
184
|
+
canisters: [
|
|
185
|
+
{
|
|
186
|
+
name: "backend",
|
|
187
|
+
didFile: "./backend/backend.did",
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
}),
|
|
191
|
+
],
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This generates method-specific hooks:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import {
|
|
199
|
+
useGetMessageQuery,
|
|
200
|
+
useSetMessageMutation,
|
|
201
|
+
getMessageQuery, // Static query for no-arg methods
|
|
202
|
+
} from "./canisters/backend"
|
|
203
|
+
|
|
204
|
+
function MyComponent() {
|
|
205
|
+
// Method-specific hook
|
|
206
|
+
const { data } = useGetMessageQuery([], { staleTime: 5000 })
|
|
207
|
+
|
|
208
|
+
// Static query usage
|
|
209
|
+
const { data: cached } = getMessageQuery.useQuery()
|
|
210
|
+
|
|
211
|
+
const { mutate } = useSetMessageMutation()
|
|
212
|
+
|
|
213
|
+
return <div>{data}</div>
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Generated File Structure
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
src/
|
|
221
|
+
├── canisters/
|
|
222
|
+
│ └── backend/
|
|
223
|
+
│ ├── index.ts # Reactor + hooks
|
|
224
|
+
│ └── declarations/
|
|
225
|
+
│ └── backend.did.ts # Type declarations
|
|
226
|
+
├── lib/
|
|
227
|
+
│ └── client.ts # Your ClientManager (not generated)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## How It Works
|
|
231
|
+
|
|
232
|
+
1. **Build Start**: The plugin reads your `.did` files and uses `@icp-sdk/bindgen` to generate TypeScript declarations
|
|
233
|
+
2. **Code Generation**: Creates a reactor instance and typed hooks for each canister
|
|
234
|
+
3. **Hot Reload**: Watches for `.did` file changes and regenerates hooks automatically
|
|
235
|
+
|
|
236
|
+
## DisplayReactor vs Reactor
|
|
237
|
+
|
|
238
|
+
By default, the plugin uses `DisplayReactor` which transforms Candid types into React-friendly formats:
|
|
239
|
+
|
|
240
|
+
| Candid Type | Reactor | DisplayReactor |
|
|
241
|
+
| ----------- | ------------ | -------------- |
|
|
242
|
+
| `nat` | `bigint` | `string` |
|
|
243
|
+
| `int` | `bigint` | `string` |
|
|
244
|
+
| `principal` | `Principal` | `string` |
|
|
245
|
+
| `vec nat8` | `Uint8Array` | `string` (hex) |
|
|
246
|
+
|
|
247
|
+
To use raw Candid types:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
icReactorPlugin({
|
|
251
|
+
canisters: [
|
|
252
|
+
{
|
|
253
|
+
name: "backend",
|
|
254
|
+
didFile: "./backend/backend.did",
|
|
255
|
+
useDisplayReactor: false, // Use Reactor instead
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Integration with ICP CLI
|
|
262
|
+
|
|
263
|
+
If you're using [icp-cli](https://github.com/AstroxNetwork/icp-cli), configure your `vite.config.ts` to pass canister IDs via the `ic_env` cookie:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// vite.config.ts
|
|
267
|
+
import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
268
|
+
import fs from "fs"
|
|
269
|
+
import path from "path"
|
|
270
|
+
|
|
271
|
+
function loadCanisterIds(): Record<string, string> {
|
|
272
|
+
const idsPath = path.resolve(__dirname, ".icp/cache/mappings/local.ids.json")
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(fs.readFileSync(idsPath, "utf-8"))
|
|
275
|
+
} catch {
|
|
276
|
+
return {}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const canisterIds = loadCanisterIds()
|
|
281
|
+
|
|
282
|
+
export default defineConfig({
|
|
283
|
+
plugins: [
|
|
284
|
+
icReactorPlugin({
|
|
285
|
+
canisters: [
|
|
286
|
+
{
|
|
287
|
+
name: "backend",
|
|
288
|
+
didFile: "./backend/backend.did",
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
}),
|
|
292
|
+
],
|
|
293
|
+
server: {
|
|
294
|
+
headers: {
|
|
295
|
+
"Set-Cookie": `ic_env=${encodeURIComponent(
|
|
296
|
+
Object.entries(canisterIds)
|
|
297
|
+
.map(([name, id]) => `PUBLIC_CANISTER_ID:${name}=${id}`)
|
|
298
|
+
.join("&")
|
|
299
|
+
)}; SameSite=Lax;`,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Requirements
|
|
306
|
+
|
|
307
|
+
- **Vite 5.x, 6.x, or 7.x**
|
|
308
|
+
- **Node.js 18+**
|
|
309
|
+
- **TypeScript 5.0+**
|
|
310
|
+
|
|
311
|
+
## Related Packages
|
|
312
|
+
|
|
313
|
+
- [@ic-reactor/react](https://www.npmjs.com/package/@ic-reactor/react) — React hooks for IC
|
|
314
|
+
- [@ic-reactor/core](https://www.npmjs.com/package/@ic-reactor/core) — Core reactor functionality
|
|
315
|
+
- [@icp-sdk/bindgen](https://www.npmjs.com/package/@icp-sdk/bindgen) — Candid binding generator
|
|
316
|
+
|
|
317
|
+
## Documentation
|
|
318
|
+
|
|
319
|
+
For comprehensive guides and API reference, visit the [documentation site](https://b3pay.github.io/ic-reactor/v3).
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT © [Behrad Deylami](https://github.com/b3hr4d)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
icReactorAdvancedPlugin: () => icReactorAdvancedPlugin,
|
|
34
|
+
icReactorPlugin: () => icReactorPlugin
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/simple.ts
|
|
39
|
+
var import_core2 = require("@icp-sdk/bindgen/core");
|
|
40
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
41
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
42
|
+
|
|
43
|
+
// src/advanced.ts
|
|
44
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
45
|
+
var import_path = __toESM(require("path"), 1);
|
|
46
|
+
var import_core = require("@icp-sdk/bindgen/core");
|
|
47
|
+
function toPascalCase(str) {
|
|
48
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
49
|
+
}
|
|
50
|
+
function toCamelCase(str) {
|
|
51
|
+
const pascal = toPascalCase(str);
|
|
52
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
53
|
+
}
|
|
54
|
+
function extractMethods(didContent) {
|
|
55
|
+
const methodRegex = /([a-zA-Z0-9_]+)\s*:\s*(?:func\s*)?\(([\s\S]*?)\)\s*->\s*\(([\s\S]*?)\)\s*(query|composite_query)?/g;
|
|
56
|
+
const methods = [];
|
|
57
|
+
let match;
|
|
58
|
+
while ((match = methodRegex.exec(didContent)) !== null) {
|
|
59
|
+
const name = match[1];
|
|
60
|
+
const args = match[2].trim();
|
|
61
|
+
const isQuery = !!match[4];
|
|
62
|
+
methods.push({
|
|
63
|
+
name,
|
|
64
|
+
type: isQuery ? "query" : "mutation",
|
|
65
|
+
hasArgs: args.length > 0
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return methods;
|
|
69
|
+
}
|
|
70
|
+
function generateAdvancedReactorFile(canisterName, useDisplayReactor, clientManagerPath, didContent) {
|
|
71
|
+
const pascalName = toPascalCase(canisterName);
|
|
72
|
+
const camelName = toCamelCase(canisterName);
|
|
73
|
+
const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
|
|
74
|
+
const methods = extractMethods(didContent);
|
|
75
|
+
const hooks = methods.map(({ name, type, hasArgs }) => {
|
|
76
|
+
const pascalMethod = toPascalCase(name);
|
|
77
|
+
const camelMethod = toCamelCase(name);
|
|
78
|
+
if (type === "query") {
|
|
79
|
+
const hook = `
|
|
80
|
+
export const use${pascalMethod}Query = (
|
|
81
|
+
args: Parameters<${pascalName}Service["${name}"]>,
|
|
82
|
+
options?: any
|
|
83
|
+
) =>
|
|
84
|
+
useActorQuery({
|
|
85
|
+
functionName: "${name}",
|
|
86
|
+
args,
|
|
87
|
+
...options,
|
|
88
|
+
})
|
|
89
|
+
`;
|
|
90
|
+
const staticQuery = !hasArgs ? `
|
|
91
|
+
export const ${camelMethod}Query = createQuery(${camelName}Reactor, {
|
|
92
|
+
functionName: "${name}",
|
|
93
|
+
})
|
|
94
|
+
` : "";
|
|
95
|
+
return hook + staticQuery;
|
|
96
|
+
} else {
|
|
97
|
+
const hook = `
|
|
98
|
+
export const use${pascalMethod}Mutation = (
|
|
99
|
+
options?: any
|
|
100
|
+
) =>
|
|
101
|
+
useActorMutation({
|
|
102
|
+
functionName: "${name}",
|
|
103
|
+
...options,
|
|
104
|
+
})
|
|
105
|
+
`;
|
|
106
|
+
const staticMutation = !hasArgs ? `
|
|
107
|
+
export const ${camelMethod}Mutation = createMutation(${camelName}Reactor, {
|
|
108
|
+
functionName: "${name}",
|
|
109
|
+
})
|
|
110
|
+
` : "";
|
|
111
|
+
return hook + staticMutation;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return `/**
|
|
115
|
+
* AUTO-GENERATED BY @ic-reactor/vite-plugin
|
|
116
|
+
* DO NOT EDIT MANUALLY
|
|
117
|
+
*
|
|
118
|
+
* Canister: ${canisterName}
|
|
119
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
120
|
+
*
|
|
121
|
+
* This file provides type-safe React hooks for interacting with the
|
|
122
|
+
* ${canisterName} canister using ic-reactor.
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
import {
|
|
126
|
+
${reactorType},
|
|
127
|
+
createActorHooks,
|
|
128
|
+
createAuthHooks,
|
|
129
|
+
createQuery,
|
|
130
|
+
createMutation,
|
|
131
|
+
} from "@ic-reactor/react"
|
|
132
|
+
|
|
133
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
134
|
+
// USER-PROVIDED CLIENT MANAGER
|
|
135
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
136
|
+
import { clientManager } from "${clientManagerPath}"
|
|
137
|
+
|
|
138
|
+
// Import generated declarations from @icp-sdk/bindgen
|
|
139
|
+
import {
|
|
140
|
+
idlFactory,
|
|
141
|
+
type _SERVICE,
|
|
142
|
+
} from "./declarations/${canisterName}.did"
|
|
143
|
+
|
|
144
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
145
|
+
// REACTOR INSTANCE
|
|
146
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
147
|
+
type ${pascalName}Service = _SERVICE
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
|
|
151
|
+
* ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
|
|
152
|
+
*/
|
|
153
|
+
export const ${camelName}Reactor = new ${reactorType}<${pascalName}Service>({
|
|
154
|
+
clientManager,
|
|
155
|
+
idlFactory,
|
|
156
|
+
name: "${canisterName}",
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
160
|
+
// ACTOR & AUTH HOOKS
|
|
161
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
162
|
+
export const {
|
|
163
|
+
useActorQuery,
|
|
164
|
+
useActorMutation,
|
|
165
|
+
useActorSuspenseQuery,
|
|
166
|
+
useActorInfiniteQuery,
|
|
167
|
+
useActorSuspenseInfiniteQuery,
|
|
168
|
+
useActorMethod,
|
|
169
|
+
} = createActorHooks(${camelName}Reactor)
|
|
170
|
+
|
|
171
|
+
export const use${pascalName}Query = useActorQuery
|
|
172
|
+
export const use${pascalName}Mutation = useActorMutation
|
|
173
|
+
export const use${pascalName}SuspenseQuery = useActorSuspenseQuery
|
|
174
|
+
export const use${pascalName}InfiniteQuery = useActorInfiniteQuery
|
|
175
|
+
export const use${pascalName}SuspenseInfiniteQuery = useActorSuspenseInfiniteQuery
|
|
176
|
+
export const use${pascalName}Method = useActorMethod
|
|
177
|
+
|
|
178
|
+
export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(
|
|
179
|
+
clientManager
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
183
|
+
// METHOD HOOKS
|
|
184
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
185
|
+
${hooks.join("")}
|
|
186
|
+
|
|
187
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
188
|
+
// RE-EXPORTS
|
|
189
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
190
|
+
export { idlFactory }
|
|
191
|
+
export type { ${pascalName}Service }
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
function icReactorAdvancedPlugin(options) {
|
|
195
|
+
const baseOutDir = options.outDir ?? "./src/canisters";
|
|
196
|
+
return {
|
|
197
|
+
name: "ic-reactor-advanced-plugin",
|
|
198
|
+
async buildStart() {
|
|
199
|
+
for (const canister of options.canisters) {
|
|
200
|
+
const outDir = canister.outDir ?? import_path.default.join(baseOutDir, canister.name);
|
|
201
|
+
if (!import_fs.default.existsSync(outDir)) {
|
|
202
|
+
import_fs.default.mkdirSync(outDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
console.log(
|
|
205
|
+
`[ic-reactor] Generating advanced hooks for ${canister.name} from ${canister.didFile}`
|
|
206
|
+
);
|
|
207
|
+
try {
|
|
208
|
+
await (0, import_core.generate)({
|
|
209
|
+
didFile: canister.didFile,
|
|
210
|
+
outDir,
|
|
211
|
+
output: {
|
|
212
|
+
actor: {
|
|
213
|
+
disabled: true
|
|
214
|
+
},
|
|
215
|
+
force: true
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
console.log(
|
|
219
|
+
`[ic-reactor] Declarations generated at ${import_path.default.join(
|
|
220
|
+
outDir,
|
|
221
|
+
"declarations"
|
|
222
|
+
)}`
|
|
223
|
+
);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error(`[ic-reactor] Failed to generate declarations:`, error);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
|
|
229
|
+
let didContent = "";
|
|
230
|
+
try {
|
|
231
|
+
didContent = import_fs.default.readFileSync(canister.didFile, "utf-8");
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.warn(
|
|
234
|
+
`[ic-reactor] Could not read DID file at ${canister.didFile}, skipping hook generation.`
|
|
235
|
+
);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const reactorContent = generateAdvancedReactorFile(
|
|
239
|
+
canister.name,
|
|
240
|
+
canister.useDisplayReactor ?? true,
|
|
241
|
+
clientManagerPath,
|
|
242
|
+
didContent
|
|
243
|
+
);
|
|
244
|
+
const reactorPath = import_path.default.join(outDir, "index.ts");
|
|
245
|
+
import_fs.default.writeFileSync(reactorPath, reactorContent);
|
|
246
|
+
console.log(
|
|
247
|
+
`[ic-reactor] Advanced reactor hooks generated at ${reactorPath}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
handleHotUpdate({ file, server }) {
|
|
252
|
+
if (file.endsWith(".did")) {
|
|
253
|
+
const canister = options.canisters.find(
|
|
254
|
+
(c) => import_path.default.resolve(c.didFile) === file
|
|
255
|
+
);
|
|
256
|
+
if (canister) {
|
|
257
|
+
console.log(
|
|
258
|
+
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
259
|
+
);
|
|
260
|
+
server.restart();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/simple.ts
|
|
268
|
+
function toPascalCase2(str) {
|
|
269
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
270
|
+
}
|
|
271
|
+
function toCamelCase2(str) {
|
|
272
|
+
const pascal = toPascalCase2(str);
|
|
273
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
274
|
+
}
|
|
275
|
+
function generateReactorFile(canisterName, useDisplayReactor, clientManagerPath) {
|
|
276
|
+
const pascalName = toPascalCase2(canisterName);
|
|
277
|
+
const camelName = toCamelCase2(canisterName);
|
|
278
|
+
const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
|
|
279
|
+
return `/**
|
|
280
|
+
* AUTO-GENERATED BY @ic-reactor/vite-plugin
|
|
281
|
+
*
|
|
282
|
+
* Canister: ${canisterName}
|
|
283
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
284
|
+
*
|
|
285
|
+
* This file provides type-safe React hooks for interacting with the
|
|
286
|
+
* ${canisterName} canister using ic-reactor.
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
import {
|
|
290
|
+
${reactorType},
|
|
291
|
+
createActorHooks,
|
|
292
|
+
} from "@ic-reactor/react"
|
|
293
|
+
|
|
294
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
295
|
+
// USER-PROVIDED CLIENT MANAGER
|
|
296
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
297
|
+
// The clientManager is imported from the user's own configuration file.
|
|
298
|
+
// This allows full customization of agent options, network settings, etc.
|
|
299
|
+
import { clientManager } from "${clientManagerPath}"
|
|
300
|
+
|
|
301
|
+
// Import generated declarations from @icp-sdk/bindgen
|
|
302
|
+
import {
|
|
303
|
+
idlFactory,
|
|
304
|
+
type _SERVICE,
|
|
305
|
+
} from "./declarations/${canisterName}.did"
|
|
306
|
+
|
|
307
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
308
|
+
// REACTOR INSTANCE
|
|
309
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
|
|
313
|
+
* ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
|
|
314
|
+
*/
|
|
315
|
+
export const ${camelName}Reactor = new ${reactorType}<_SERVICE>({
|
|
316
|
+
clientManager,
|
|
317
|
+
idlFactory,
|
|
318
|
+
name: "${canisterName}",
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
322
|
+
// HOOKS
|
|
323
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* React hooks for the ${canisterName} canister.
|
|
327
|
+
*/
|
|
328
|
+
const {
|
|
329
|
+
useActorQuery: use${pascalName}Query,
|
|
330
|
+
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
331
|
+
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
332
|
+
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
333
|
+
useActorMutation: use${pascalName}Mutation,
|
|
334
|
+
useActorMethod: use${pascalName}Method,
|
|
335
|
+
} = createActorHooks(${camelName}Reactor)
|
|
336
|
+
|
|
337
|
+
export {
|
|
338
|
+
use${pascalName}Query,
|
|
339
|
+
use${pascalName}SuspenseQuery,
|
|
340
|
+
use${pascalName}InfiniteQuery,
|
|
341
|
+
use${pascalName}SuspenseInfiniteQuery,
|
|
342
|
+
use${pascalName}Mutation,
|
|
343
|
+
use${pascalName}Method,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
347
|
+
// RE-EXPORTS
|
|
348
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
349
|
+
|
|
350
|
+
export { idlFactory }
|
|
351
|
+
export type { _SERVICE as ${pascalName}Service }
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
function icReactorPlugin(options) {
|
|
355
|
+
const baseOutDir = options.outDir ?? "./src/canisters";
|
|
356
|
+
return {
|
|
357
|
+
name: "ic-reactor-plugin",
|
|
358
|
+
async buildStart() {
|
|
359
|
+
for (const canister of options.canisters) {
|
|
360
|
+
const outDir = canister.outDir ?? import_path2.default.join(baseOutDir, canister.name);
|
|
361
|
+
const declarationsDir = import_path2.default.join(outDir, "declarations");
|
|
362
|
+
console.log(
|
|
363
|
+
`[ic-reactor] Generating hooks for ${canister.name} from ${canister.didFile}`
|
|
364
|
+
);
|
|
365
|
+
if (import_fs2.default.existsSync(declarationsDir)) {
|
|
366
|
+
import_fs2.default.rmSync(declarationsDir, { recursive: true, force: true });
|
|
367
|
+
}
|
|
368
|
+
import_fs2.default.mkdirSync(declarationsDir, { recursive: true });
|
|
369
|
+
try {
|
|
370
|
+
if (!import_fs2.default.existsSync(outDir)) {
|
|
371
|
+
import_fs2.default.mkdirSync(outDir, { recursive: true });
|
|
372
|
+
}
|
|
373
|
+
await (0, import_core2.generate)({
|
|
374
|
+
didFile: canister.didFile,
|
|
375
|
+
outDir,
|
|
376
|
+
// Pass the parent directory; bindgen appends "declarations"
|
|
377
|
+
output: {
|
|
378
|
+
actor: {
|
|
379
|
+
disabled: true
|
|
380
|
+
},
|
|
381
|
+
force: true
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
console.log(
|
|
385
|
+
`[ic-reactor] Declarations generated at ${declarationsDir}`
|
|
386
|
+
);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error(`[ic-reactor] Failed to generate declarations:`, error);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
|
|
392
|
+
const reactorContent = generateReactorFile(
|
|
393
|
+
canister.name,
|
|
394
|
+
canister.useDisplayReactor ?? true,
|
|
395
|
+
clientManagerPath
|
|
396
|
+
);
|
|
397
|
+
const reactorPath = import_path2.default.join(outDir, "index.ts");
|
|
398
|
+
import_fs2.default.mkdirSync(outDir, { recursive: true });
|
|
399
|
+
import_fs2.default.writeFileSync(reactorPath, reactorContent);
|
|
400
|
+
console.log(`[ic-reactor] Reactor hooks generated at ${reactorPath}`);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
handleHotUpdate({ file, server }) {
|
|
404
|
+
if (file.endsWith(".did")) {
|
|
405
|
+
const canister = options.canisters.find(
|
|
406
|
+
(c) => import_path2.default.resolve(c.didFile) === file
|
|
407
|
+
);
|
|
408
|
+
if (canister) {
|
|
409
|
+
console.log(
|
|
410
|
+
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
411
|
+
);
|
|
412
|
+
server.restart();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
419
|
+
0 && (module.exports = {
|
|
420
|
+
icReactorAdvancedPlugin,
|
|
421
|
+
icReactorPlugin
|
|
422
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
declare function icReactorAdvancedPlugin(options: IcReactorPluginOptions): Plugin;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IC-Reactor Vite Plugin
|
|
7
|
+
*
|
|
8
|
+
* A Vite plugin that generates ic-reactor hooks from Candid .did files.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ IMPORTANT: This plugin ONLY generates the reactor and hooks.
|
|
11
|
+
* The user is responsible for creating and configuring:
|
|
12
|
+
* - ClientManager
|
|
13
|
+
* - QueryClient
|
|
14
|
+
*
|
|
15
|
+
* The generated file will import the clientManager from a user-specified path.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig({
|
|
22
|
+
* plugins: [
|
|
23
|
+
* icReactorPlugin({
|
|
24
|
+
* canisters: [
|
|
25
|
+
* {
|
|
26
|
+
* name: "backend",
|
|
27
|
+
* didFile: "../backend/backend.did",
|
|
28
|
+
* clientManagerPath: "../lib/client" // User provides their own ClientManager
|
|
29
|
+
* }
|
|
30
|
+
* ]
|
|
31
|
+
* })
|
|
32
|
+
* ]
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface CanisterConfig {
|
|
38
|
+
/** Name of the canister (used for variable naming) */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Path to the .did file */
|
|
41
|
+
didFile: string;
|
|
42
|
+
/** Output directory (default: ./src/canisters/<name>) */
|
|
43
|
+
outDir?: string;
|
|
44
|
+
/** Use DisplayReactor for React-friendly types (default: true) */
|
|
45
|
+
useDisplayReactor?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Path to import ClientManager from (relative to generated file).
|
|
48
|
+
* The file at this path should export: { clientManager: ClientManager }
|
|
49
|
+
* Default: "../../lib/client"
|
|
50
|
+
*/
|
|
51
|
+
clientManagerPath?: string;
|
|
52
|
+
}
|
|
53
|
+
interface IcReactorPluginOptions {
|
|
54
|
+
/** List of canisters to generate hooks for */
|
|
55
|
+
canisters: CanisterConfig[];
|
|
56
|
+
/** Base output directory (default: ./src/canisters) */
|
|
57
|
+
outDir?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Path to import ClientManager from (relative to generated file).
|
|
60
|
+
* The file at this path should export: { clientManager: ClientManager }
|
|
61
|
+
* Default: "../../lib/client"
|
|
62
|
+
*/
|
|
63
|
+
clientManagerPath?: string;
|
|
64
|
+
}
|
|
65
|
+
declare function icReactorPlugin(options: IcReactorPluginOptions): Plugin;
|
|
66
|
+
|
|
67
|
+
export { type CanisterConfig, type IcReactorPluginOptions, icReactorAdvancedPlugin, icReactorPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
declare function icReactorAdvancedPlugin(options: IcReactorPluginOptions): Plugin;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IC-Reactor Vite Plugin
|
|
7
|
+
*
|
|
8
|
+
* A Vite plugin that generates ic-reactor hooks from Candid .did files.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ IMPORTANT: This plugin ONLY generates the reactor and hooks.
|
|
11
|
+
* The user is responsible for creating and configuring:
|
|
12
|
+
* - ClientManager
|
|
13
|
+
* - QueryClient
|
|
14
|
+
*
|
|
15
|
+
* The generated file will import the clientManager from a user-specified path.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { icReactorPlugin } from "@ic-reactor/vite-plugin"
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig({
|
|
22
|
+
* plugins: [
|
|
23
|
+
* icReactorPlugin({
|
|
24
|
+
* canisters: [
|
|
25
|
+
* {
|
|
26
|
+
* name: "backend",
|
|
27
|
+
* didFile: "../backend/backend.did",
|
|
28
|
+
* clientManagerPath: "../lib/client" // User provides their own ClientManager
|
|
29
|
+
* }
|
|
30
|
+
* ]
|
|
31
|
+
* })
|
|
32
|
+
* ]
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface CanisterConfig {
|
|
38
|
+
/** Name of the canister (used for variable naming) */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Path to the .did file */
|
|
41
|
+
didFile: string;
|
|
42
|
+
/** Output directory (default: ./src/canisters/<name>) */
|
|
43
|
+
outDir?: string;
|
|
44
|
+
/** Use DisplayReactor for React-friendly types (default: true) */
|
|
45
|
+
useDisplayReactor?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Path to import ClientManager from (relative to generated file).
|
|
48
|
+
* The file at this path should export: { clientManager: ClientManager }
|
|
49
|
+
* Default: "../../lib/client"
|
|
50
|
+
*/
|
|
51
|
+
clientManagerPath?: string;
|
|
52
|
+
}
|
|
53
|
+
interface IcReactorPluginOptions {
|
|
54
|
+
/** List of canisters to generate hooks for */
|
|
55
|
+
canisters: CanisterConfig[];
|
|
56
|
+
/** Base output directory (default: ./src/canisters) */
|
|
57
|
+
outDir?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Path to import ClientManager from (relative to generated file).
|
|
60
|
+
* The file at this path should export: { clientManager: ClientManager }
|
|
61
|
+
* Default: "../../lib/client"
|
|
62
|
+
*/
|
|
63
|
+
clientManagerPath?: string;
|
|
64
|
+
}
|
|
65
|
+
declare function icReactorPlugin(options: IcReactorPluginOptions): Plugin;
|
|
66
|
+
|
|
67
|
+
export { type CanisterConfig, type IcReactorPluginOptions, icReactorAdvancedPlugin, icReactorPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// src/simple.ts
|
|
2
|
+
import { generate as generate2 } from "@icp-sdk/bindgen/core";
|
|
3
|
+
import fs2 from "fs";
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
|
|
6
|
+
// src/advanced.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { generate } from "@icp-sdk/bindgen/core";
|
|
10
|
+
function toPascalCase(str) {
|
|
11
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
12
|
+
}
|
|
13
|
+
function toCamelCase(str) {
|
|
14
|
+
const pascal = toPascalCase(str);
|
|
15
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
16
|
+
}
|
|
17
|
+
function extractMethods(didContent) {
|
|
18
|
+
const methodRegex = /([a-zA-Z0-9_]+)\s*:\s*(?:func\s*)?\(([\s\S]*?)\)\s*->\s*\(([\s\S]*?)\)\s*(query|composite_query)?/g;
|
|
19
|
+
const methods = [];
|
|
20
|
+
let match;
|
|
21
|
+
while ((match = methodRegex.exec(didContent)) !== null) {
|
|
22
|
+
const name = match[1];
|
|
23
|
+
const args = match[2].trim();
|
|
24
|
+
const isQuery = !!match[4];
|
|
25
|
+
methods.push({
|
|
26
|
+
name,
|
|
27
|
+
type: isQuery ? "query" : "mutation",
|
|
28
|
+
hasArgs: args.length > 0
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return methods;
|
|
32
|
+
}
|
|
33
|
+
function generateAdvancedReactorFile(canisterName, useDisplayReactor, clientManagerPath, didContent) {
|
|
34
|
+
const pascalName = toPascalCase(canisterName);
|
|
35
|
+
const camelName = toCamelCase(canisterName);
|
|
36
|
+
const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
|
|
37
|
+
const methods = extractMethods(didContent);
|
|
38
|
+
const hooks = methods.map(({ name, type, hasArgs }) => {
|
|
39
|
+
const pascalMethod = toPascalCase(name);
|
|
40
|
+
const camelMethod = toCamelCase(name);
|
|
41
|
+
if (type === "query") {
|
|
42
|
+
const hook = `
|
|
43
|
+
export const use${pascalMethod}Query = (
|
|
44
|
+
args: Parameters<${pascalName}Service["${name}"]>,
|
|
45
|
+
options?: any
|
|
46
|
+
) =>
|
|
47
|
+
useActorQuery({
|
|
48
|
+
functionName: "${name}",
|
|
49
|
+
args,
|
|
50
|
+
...options,
|
|
51
|
+
})
|
|
52
|
+
`;
|
|
53
|
+
const staticQuery = !hasArgs ? `
|
|
54
|
+
export const ${camelMethod}Query = createQuery(${camelName}Reactor, {
|
|
55
|
+
functionName: "${name}",
|
|
56
|
+
})
|
|
57
|
+
` : "";
|
|
58
|
+
return hook + staticQuery;
|
|
59
|
+
} else {
|
|
60
|
+
const hook = `
|
|
61
|
+
export const use${pascalMethod}Mutation = (
|
|
62
|
+
options?: any
|
|
63
|
+
) =>
|
|
64
|
+
useActorMutation({
|
|
65
|
+
functionName: "${name}",
|
|
66
|
+
...options,
|
|
67
|
+
})
|
|
68
|
+
`;
|
|
69
|
+
const staticMutation = !hasArgs ? `
|
|
70
|
+
export const ${camelMethod}Mutation = createMutation(${camelName}Reactor, {
|
|
71
|
+
functionName: "${name}",
|
|
72
|
+
})
|
|
73
|
+
` : "";
|
|
74
|
+
return hook + staticMutation;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return `/**
|
|
78
|
+
* AUTO-GENERATED BY @ic-reactor/vite-plugin
|
|
79
|
+
* DO NOT EDIT MANUALLY
|
|
80
|
+
*
|
|
81
|
+
* Canister: ${canisterName}
|
|
82
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
83
|
+
*
|
|
84
|
+
* This file provides type-safe React hooks for interacting with the
|
|
85
|
+
* ${canisterName} canister using ic-reactor.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
import {
|
|
89
|
+
${reactorType},
|
|
90
|
+
createActorHooks,
|
|
91
|
+
createAuthHooks,
|
|
92
|
+
createQuery,
|
|
93
|
+
createMutation,
|
|
94
|
+
} from "@ic-reactor/react"
|
|
95
|
+
|
|
96
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
97
|
+
// USER-PROVIDED CLIENT MANAGER
|
|
98
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
99
|
+
import { clientManager } from "${clientManagerPath}"
|
|
100
|
+
|
|
101
|
+
// Import generated declarations from @icp-sdk/bindgen
|
|
102
|
+
import {
|
|
103
|
+
idlFactory,
|
|
104
|
+
type _SERVICE,
|
|
105
|
+
} from "./declarations/${canisterName}.did"
|
|
106
|
+
|
|
107
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
108
|
+
// REACTOR INSTANCE
|
|
109
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
110
|
+
type ${pascalName}Service = _SERVICE
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
|
|
114
|
+
* ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
|
|
115
|
+
*/
|
|
116
|
+
export const ${camelName}Reactor = new ${reactorType}<${pascalName}Service>({
|
|
117
|
+
clientManager,
|
|
118
|
+
idlFactory,
|
|
119
|
+
name: "${canisterName}",
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
123
|
+
// ACTOR & AUTH HOOKS
|
|
124
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
125
|
+
export const {
|
|
126
|
+
useActorQuery,
|
|
127
|
+
useActorMutation,
|
|
128
|
+
useActorSuspenseQuery,
|
|
129
|
+
useActorInfiniteQuery,
|
|
130
|
+
useActorSuspenseInfiniteQuery,
|
|
131
|
+
useActorMethod,
|
|
132
|
+
} = createActorHooks(${camelName}Reactor)
|
|
133
|
+
|
|
134
|
+
export const use${pascalName}Query = useActorQuery
|
|
135
|
+
export const use${pascalName}Mutation = useActorMutation
|
|
136
|
+
export const use${pascalName}SuspenseQuery = useActorSuspenseQuery
|
|
137
|
+
export const use${pascalName}InfiniteQuery = useActorInfiniteQuery
|
|
138
|
+
export const use${pascalName}SuspenseInfiniteQuery = useActorSuspenseInfiniteQuery
|
|
139
|
+
export const use${pascalName}Method = useActorMethod
|
|
140
|
+
|
|
141
|
+
export const { useAuth, useAgentState, useUserPrincipal } = createAuthHooks(
|
|
142
|
+
clientManager
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
146
|
+
// METHOD HOOKS
|
|
147
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
148
|
+
${hooks.join("")}
|
|
149
|
+
|
|
150
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
151
|
+
// RE-EXPORTS
|
|
152
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
153
|
+
export { idlFactory }
|
|
154
|
+
export type { ${pascalName}Service }
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
function icReactorAdvancedPlugin(options) {
|
|
158
|
+
const baseOutDir = options.outDir ?? "./src/canisters";
|
|
159
|
+
return {
|
|
160
|
+
name: "ic-reactor-advanced-plugin",
|
|
161
|
+
async buildStart() {
|
|
162
|
+
for (const canister of options.canisters) {
|
|
163
|
+
const outDir = canister.outDir ?? path.join(baseOutDir, canister.name);
|
|
164
|
+
if (!fs.existsSync(outDir)) {
|
|
165
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
console.log(
|
|
168
|
+
`[ic-reactor] Generating advanced hooks for ${canister.name} from ${canister.didFile}`
|
|
169
|
+
);
|
|
170
|
+
try {
|
|
171
|
+
await generate({
|
|
172
|
+
didFile: canister.didFile,
|
|
173
|
+
outDir,
|
|
174
|
+
output: {
|
|
175
|
+
actor: {
|
|
176
|
+
disabled: true
|
|
177
|
+
},
|
|
178
|
+
force: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
console.log(
|
|
182
|
+
`[ic-reactor] Declarations generated at ${path.join(
|
|
183
|
+
outDir,
|
|
184
|
+
"declarations"
|
|
185
|
+
)}`
|
|
186
|
+
);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`[ic-reactor] Failed to generate declarations:`, error);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
|
|
192
|
+
let didContent = "";
|
|
193
|
+
try {
|
|
194
|
+
didContent = fs.readFileSync(canister.didFile, "utf-8");
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.warn(
|
|
197
|
+
`[ic-reactor] Could not read DID file at ${canister.didFile}, skipping hook generation.`
|
|
198
|
+
);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const reactorContent = generateAdvancedReactorFile(
|
|
202
|
+
canister.name,
|
|
203
|
+
canister.useDisplayReactor ?? true,
|
|
204
|
+
clientManagerPath,
|
|
205
|
+
didContent
|
|
206
|
+
);
|
|
207
|
+
const reactorPath = path.join(outDir, "index.ts");
|
|
208
|
+
fs.writeFileSync(reactorPath, reactorContent);
|
|
209
|
+
console.log(
|
|
210
|
+
`[ic-reactor] Advanced reactor hooks generated at ${reactorPath}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
handleHotUpdate({ file, server }) {
|
|
215
|
+
if (file.endsWith(".did")) {
|
|
216
|
+
const canister = options.canisters.find(
|
|
217
|
+
(c) => path.resolve(c.didFile) === file
|
|
218
|
+
);
|
|
219
|
+
if (canister) {
|
|
220
|
+
console.log(
|
|
221
|
+
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
222
|
+
);
|
|
223
|
+
server.restart();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/simple.ts
|
|
231
|
+
function toPascalCase2(str) {
|
|
232
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
233
|
+
}
|
|
234
|
+
function toCamelCase2(str) {
|
|
235
|
+
const pascal = toPascalCase2(str);
|
|
236
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
237
|
+
}
|
|
238
|
+
function generateReactorFile(canisterName, useDisplayReactor, clientManagerPath) {
|
|
239
|
+
const pascalName = toPascalCase2(canisterName);
|
|
240
|
+
const camelName = toCamelCase2(canisterName);
|
|
241
|
+
const reactorType = useDisplayReactor ? "DisplayReactor" : "Reactor";
|
|
242
|
+
return `/**
|
|
243
|
+
* AUTO-GENERATED BY @ic-reactor/vite-plugin
|
|
244
|
+
*
|
|
245
|
+
* Canister: ${canisterName}
|
|
246
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
247
|
+
*
|
|
248
|
+
* This file provides type-safe React hooks for interacting with the
|
|
249
|
+
* ${canisterName} canister using ic-reactor.
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
import {
|
|
253
|
+
${reactorType},
|
|
254
|
+
createActorHooks,
|
|
255
|
+
} from "@ic-reactor/react"
|
|
256
|
+
|
|
257
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
258
|
+
// USER-PROVIDED CLIENT MANAGER
|
|
259
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
260
|
+
// The clientManager is imported from the user's own configuration file.
|
|
261
|
+
// This allows full customization of agent options, network settings, etc.
|
|
262
|
+
import { clientManager } from "${clientManagerPath}"
|
|
263
|
+
|
|
264
|
+
// Import generated declarations from @icp-sdk/bindgen
|
|
265
|
+
import {
|
|
266
|
+
idlFactory,
|
|
267
|
+
type _SERVICE,
|
|
268
|
+
} from "./declarations/${canisterName}.did"
|
|
269
|
+
|
|
270
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
271
|
+
// REACTOR INSTANCE
|
|
272
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* ${pascalName} Reactor with ${useDisplayReactor ? "Display" : "Candid"} type transformations.
|
|
276
|
+
* ${useDisplayReactor ? "Automatically converts bigint \u2192 string, Principal \u2192 string, etc." : "Uses raw Candid types."}
|
|
277
|
+
*/
|
|
278
|
+
export const ${camelName}Reactor = new ${reactorType}<_SERVICE>({
|
|
279
|
+
clientManager,
|
|
280
|
+
idlFactory,
|
|
281
|
+
name: "${canisterName}",
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
285
|
+
// HOOKS
|
|
286
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* React hooks for the ${canisterName} canister.
|
|
290
|
+
*/
|
|
291
|
+
const {
|
|
292
|
+
useActorQuery: use${pascalName}Query,
|
|
293
|
+
useActorSuspenseQuery: use${pascalName}SuspenseQuery,
|
|
294
|
+
useActorInfiniteQuery: use${pascalName}InfiniteQuery,
|
|
295
|
+
useActorSuspenseInfiniteQuery: use${pascalName}SuspenseInfiniteQuery,
|
|
296
|
+
useActorMutation: use${pascalName}Mutation,
|
|
297
|
+
useActorMethod: use${pascalName}Method,
|
|
298
|
+
} = createActorHooks(${camelName}Reactor)
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
use${pascalName}Query,
|
|
302
|
+
use${pascalName}SuspenseQuery,
|
|
303
|
+
use${pascalName}InfiniteQuery,
|
|
304
|
+
use${pascalName}SuspenseInfiniteQuery,
|
|
305
|
+
use${pascalName}Mutation,
|
|
306
|
+
use${pascalName}Method,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
310
|
+
// RE-EXPORTS
|
|
311
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
312
|
+
|
|
313
|
+
export { idlFactory }
|
|
314
|
+
export type { _SERVICE as ${pascalName}Service }
|
|
315
|
+
`;
|
|
316
|
+
}
|
|
317
|
+
function icReactorPlugin(options) {
|
|
318
|
+
const baseOutDir = options.outDir ?? "./src/canisters";
|
|
319
|
+
return {
|
|
320
|
+
name: "ic-reactor-plugin",
|
|
321
|
+
async buildStart() {
|
|
322
|
+
for (const canister of options.canisters) {
|
|
323
|
+
const outDir = canister.outDir ?? path2.join(baseOutDir, canister.name);
|
|
324
|
+
const declarationsDir = path2.join(outDir, "declarations");
|
|
325
|
+
console.log(
|
|
326
|
+
`[ic-reactor] Generating hooks for ${canister.name} from ${canister.didFile}`
|
|
327
|
+
);
|
|
328
|
+
if (fs2.existsSync(declarationsDir)) {
|
|
329
|
+
fs2.rmSync(declarationsDir, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
fs2.mkdirSync(declarationsDir, { recursive: true });
|
|
332
|
+
try {
|
|
333
|
+
if (!fs2.existsSync(outDir)) {
|
|
334
|
+
fs2.mkdirSync(outDir, { recursive: true });
|
|
335
|
+
}
|
|
336
|
+
await generate2({
|
|
337
|
+
didFile: canister.didFile,
|
|
338
|
+
outDir,
|
|
339
|
+
// Pass the parent directory; bindgen appends "declarations"
|
|
340
|
+
output: {
|
|
341
|
+
actor: {
|
|
342
|
+
disabled: true
|
|
343
|
+
},
|
|
344
|
+
force: true
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
console.log(
|
|
348
|
+
`[ic-reactor] Declarations generated at ${declarationsDir}`
|
|
349
|
+
);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(`[ic-reactor] Failed to generate declarations:`, error);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const clientManagerPath = canister.clientManagerPath ?? options.clientManagerPath ?? "../../lib/client";
|
|
355
|
+
const reactorContent = generateReactorFile(
|
|
356
|
+
canister.name,
|
|
357
|
+
canister.useDisplayReactor ?? true,
|
|
358
|
+
clientManagerPath
|
|
359
|
+
);
|
|
360
|
+
const reactorPath = path2.join(outDir, "index.ts");
|
|
361
|
+
fs2.mkdirSync(outDir, { recursive: true });
|
|
362
|
+
fs2.writeFileSync(reactorPath, reactorContent);
|
|
363
|
+
console.log(`[ic-reactor] Reactor hooks generated at ${reactorPath}`);
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
handleHotUpdate({ file, server }) {
|
|
367
|
+
if (file.endsWith(".did")) {
|
|
368
|
+
const canister = options.canisters.find(
|
|
369
|
+
(c) => path2.resolve(c.didFile) === file
|
|
370
|
+
);
|
|
371
|
+
if (canister) {
|
|
372
|
+
console.log(
|
|
373
|
+
`[ic-reactor] Detected change in ${file}, regenerating...`
|
|
374
|
+
);
|
|
375
|
+
server.restart();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
export {
|
|
382
|
+
icReactorAdvancedPlugin,
|
|
383
|
+
icReactorPlugin
|
|
384
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ic-reactor/vite-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for zero-config IC reactor generation from Candid files",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.json",
|
|
21
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch --tsconfig tsconfig.json"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"vite",
|
|
25
|
+
"vite-plugin",
|
|
26
|
+
"internet-computer",
|
|
27
|
+
"candid",
|
|
28
|
+
"ic-reactor",
|
|
29
|
+
"dfinity",
|
|
30
|
+
"icp"
|
|
31
|
+
],
|
|
32
|
+
"author": "Behrad Deylami",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@icp-sdk/bindgen": "^0.2.0",
|
|
36
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@icp-sdk/bindgen": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"chokidar": "^4.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^25.0.3",
|
|
48
|
+
"tsup": "^8.5.1",
|
|
49
|
+
"typescript": "^5.9.0",
|
|
50
|
+
"vite": "^7.3.1"
|
|
51
|
+
}
|
|
52
|
+
}
|