{"_id":"@canlooks/roost-electron-renderer","name":"@canlooks/roost-electron-renderer","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@canlooks/roost-electron-renderer","version":"0.0.1","author":{"name":"C.CanLiang","email":"canlooks@gmail.com"},"description":"A backend micro service framework","keywords":["micro service"],"main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/esm/index.d.ts","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+https://github.com/canlooks/roost.git"},"homepage":"https://github.com/canlooks/roost","bugs":{"url":"https://github.com/canlooks/roost/issues","email":"canlooks@gmail.com"},"license":"MIT","scripts":{"clean":"npx shx rm -rf dist","build":"tsc -m esnext --outDir dist/esm & tsc -m commonjs --outDir dist/cjs","build:alias":"tsc-alias --outDir dist/esm","rebuild":"npm run clean && npm run build && npm run build:alias","test":"vitest run"},"dependencies":{"@canlooks/roost":"^0.0.1","tslib":"^2.8.1"},"devDependencies":{"@types/express":"^5.0.6","@types/node":"^25.9.1","@types/react":"^19.2.17","@types/react-dom":"^19.2.3","electron":"^42.3.0","react":"^19.2.7","react-dom":"^19.2.7","tsc-alias":"^1.8.17","typescript":"^6.0.3","vite":"^8.0.16","vitest":"^4.1.7"},"_id":"@canlooks/roost-electron-renderer@0.0.1","gitHead":"505c5898b5d648b7d7d7fd033290357f58536119","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-p+2TrvkxorMDXQC3Sb2ZSTKC6bbXMqWK4uOoTFIhs1oUHds7dkfGaJwBXQ45fzXetodJUL7CnQVGaHOZi+Fu+g==","shasum":"ae2296e6b04bb6a3c8d578a4789ce7cf8d4b61b7","tarball":"https://registry.npmjs.org/@canlooks/roost-electron-renderer/-/roost-electron-renderer-0.0.1.tgz","fileCount":15,"unpackedSize":17237,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIATDJTQ7bXj83CD/gxxoaf+EgpnO6LEyjYgVjEDdv6qdAiEAu/pKWDFeceCnAFuM2Al0FhAkpR/BD6cXkeVAaADj2MM="}]},"_npmUser":{"name":"canlooks","email":"364021661@qq.com"},"directories":{},"maintainers":[{"name":"canlooks","email":"364021661@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/roost-electron-renderer_0.0.1_1781149367881_0.23230263012795227"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-11T03:42:47.758Z","0.0.1":"2026-06-11T03:42:48.017Z","modified":"2026-06-11T03:42:48.186Z"},"maintainers":[{"name":"canlooks","email":"364021661@qq.com"}],"description":"A backend micro service framework","homepage":"https://github.com/canlooks/roost","keywords":["micro service"],"repository":{"type":"git","url":"git+https://github.com/canlooks/roost.git"},"author":{"name":"C.CanLiang","email":"canlooks@gmail.com"},"bugs":{"url":"https://github.com/canlooks/roost/issues","email":"canlooks@gmail.com"},"license":"MIT","readme":"# @canlooks/roost-electron-renderer\n\nElectron renderer process plugin for the [Roost](https://github.com/canlooks/roost) micro-service framework. Provides seamless IPC-based remote procedure call (RPC) proxying — call main-process controller actions from the renderer as if they were local methods.\n\n## Overview\n\nIn an Electron application, business logic typically runs in the main process via Roost controllers decorated with `@Controller` and `@Action`. This package creates transparent **proxy instances** of those controllers in the renderer process. Every `@Action`-decorated method on a proxy is replaced with an `ipcRenderer.invoke()` call, routing arguments through Electron's IPC channel to the main process and returning the result as a promise.\n\nThe renderer code never touches IPC directly — it simply calls methods on controller instances.\n\n```\n┌─ Renderer Process ─────────────────────┐\n│                                         │\n│  const { myCtrl } =                     │\n│    await createRoostRenderer(           │\n│      { MyController },                  │\n│      { ipcRenderer }                    │\n│    )                                    │\n│                                         │\n│  // Looks like a local call...          │\n│  const result = await myCtrl.doWork(x)  │\n│                     │                   │\n└─────────────────────┼───────────────────┘\n                      │ ipcRenderer.invoke(channel, path, ...args)\n                      ▼\n┌─ Main Process ─────────────────────────┐\n│                                         │\n│  @Controller('api')                     │\n│  class MyController {                   │\n│    @Action('doWork')                    │\n│    doWork(x) { ... }                    │\n│  }                                      │\n│                                         │\n└─────────────────────────────────────────┘\n```\n\n## Installation\n\n```bash\nnpm install @canlooks/roost-electron-renderer\n```\n\n**Peer dependencies:**\n\n- [`@canlooks/roost`](https://www.npmjs.com/package/@canlooks/roost) — the core Roost framework\n- [`electron`](https://www.npmjs.com/package/electron) — provides `ipcRenderer`\n\n## API Reference\n\n### `createRoostRenderer(controllers, options)`\n\nCreates proxy controller instances whose `@Action` methods are wired to `ipcRenderer.invoke`.\n\n```typescript\nfunction createRoostRenderer<T extends Record<string, ComponentType>>(\n  controllers: T,\n  options: CreateRoostRendererOptions\n): Promise<{ [K in keyof T]: InstanceType<T[K]> }>\n```\n\n#### Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `controllers` | `Record<string, ComponentType>` | Map of controller classes keyed by name. Each class must be decorated with `@Controller` from `@canlooks/roost`. |\n| `options` | `CreateRoostRendererOptions` | Configuration for the renderer proxy. |\n\n#### Returns\n\nAn object with the same keys as the input `controllers` map, where each value is an **instance** of the corresponding controller class with its `@Action` methods rewritten to invoke IPC.\n\n#### `CreateRoostRendererOptions`\n\n| Property | Type | Required | Default | Description |\n|----------|------|----------|---------|-------------|\n| `ipcRenderer` | `IpcRenderer` | Yes | — | The Electron `ipcRenderer` instance from the renderer process. |\n| `channel` | `string` | No | `'@canlooks/roost-electron'` | Custom IPC channel name. |\n\n### `rewriteActions(instances, options)`\n\n> **Internal.** Exported for advanced use cases. Rewrites `@Action` methods on controller instances to proxy through `ipcRenderer.invoke`.\n\n```typescript\nfunction rewriteActions(\n  instances: any[],\n  options: CreateRoostRendererOptions\n): void\n```\n\n## How It Works\n\n### Action Path Resolution\n\nEach `@Action`-decorated method is mapped to an IPC key derived from the controller and action paths:\n\n| Controller Decorator | Action Decorator | Resolved IPC Key |\n|----------------------|------------------|------------------|\n| `@Controller('api')` | `@Action('hello')` | `'/api/hello'` |\n| `@Controller('users')` | `@Action('list')` | `'/users/list'` |\n| `@Controller()` | `@Action('status')` | `'/status'` |\n\nThe resolved key is passed as the second argument to `ipcRenderer.invoke(channel, key, ...args)`.\n\n### Method Rewriting\n\n- Only methods decorated with `@Action` are rewritten. Regular methods and properties are left untouched.\n- Rewriting happens **per-instance**, not on the prototype. The original class prototype remains intact.\n- Each call to a rewritten method results in a fresh `ipcRenderer.invoke()` call — no caching or batching.\n- All arguments passed to the method are forwarded as variadic arguments to `ipcRenderer.invoke`.\n- The return value of `ipcRenderer.invoke` (a `Promise`) is returned directly, preserving the original reference.\n\n### Error Propagation\n\nErrors thrown by the main process handler (or IPC errors) are propagated as rejected promises:\n\n```typescript\nconst { ctrl } = await createRoostRenderer({ TestController }, { ipcRenderer })\n\ntry {\n  await ctrl.hello('World')\n} catch (err) {\n  // err is the exact rejection from ipcRenderer.invoke\n}\n```\n\n## Usage\n\n### Basic Example\n\n```typescript\n// ═══════════════════════════════════════════════════════════════════\n// Shared controller definition (e.g., in a shared package)\n// ═══════════════════════════════════════════════════════════════════\n\nimport { Controller, Action } from '@canlooks/roost'\n\n@Controller('api')\nexport class ApiController {\n  @Action('greet')\n  greet(name: string): string {\n    return `Hello, ${name}!`\n  }\n\n  @Action('add')\n  add(a: number, b: number): number {\n    return a + b\n  }\n\n  // Regular methods are NOT proxied\n  getVersion(): string {\n    return '1.0.0'\n  }\n}\n```\n\n```typescript\n// ═══════════════════════════════════════════════════════════════════\n// Main process — sets up IPC handler with Roost\n// ═══════════════════════════════════════════════════════════════════\n\nimport { app, BrowserWindow, ipcMain } from 'electron'\nimport { Roost } from '@canlooks/roost'\n\napp.whenReady().then(async () => {\n  const roost = await Roost.create({\n    named: { ApiController }\n  })\n\n  // Handle incoming IPC calls\n  ipcMain.handle('@canlooks/roost-electron', async (_event, path, ...args) => {\n    const results = await roost.invoke(path, ...args)\n    return results[0] // Return the first result for single-action calls\n  })\n\n  // ... create BrowserWindow, load renderer\n})\n```\n\n```typescript\n// ═══════════════════════════════════════════════════════════════════\n// Renderer process — creates proxy and calls methods transparently\n// ═══════════════════════════════════════════════════════════════════\n\nimport { createRoostRenderer } from '@canlooks/roost-electron-renderer'\nimport { ipcRenderer } from 'electron'\n\nasync function main() {\n  const { ApiController: api } = await createRoostRenderer(\n    { ApiController },\n    { ipcRenderer }\n  )\n\n  // These look like local calls but go through IPC to the main process:\n  const greeting = await api.greet('World')  // → \"Hello, World!\"\n  const sum = await api.add(3, 4)            // → 7\n\n  // Non-action methods are NOT proxied — they run locally:\n  const version = api.getVersion()            // → \"1.0.0\" (local call)\n}\n```\n\n### Multiple Controllers\n\n```typescript\nconst { UserController, OrderController } = await createRoostRenderer(\n  { UserController, OrderController },\n  { ipcRenderer }\n)\n\n// Each controller's @Action methods are independently proxied\nconst users = await UserController.list()\nconst order = await OrderController.findById(42)\n```\n\n### Custom IPC Channel\n\n```typescript\nconst { ApiController: api } = await createRoostRenderer(\n  { ApiController },\n  {\n    ipcRenderer,\n    channel: 'my-custom-channel' // Must match main process ipcMain.handle()\n  }\n)\n```\n\n### Controllers Without Actions\n\nControllers with no `@Action` methods are handled gracefully — the instance is returned as-is with no method rewriting:\n\n```typescript\n@Controller('config')\nclass ConfigController {\n  theme = 'dark'\n  setTheme(t: string) { this.theme = t }\n}\n\nconst { ConfigController: config } = await createRoostRenderer(\n  { ConfigController },\n  { ipcRenderer }\n)\n\nconsole.log(config.theme) // 'dark' — normal property access\n```\n\n## TypeScript Support\n\nThe package is written in TypeScript and ships with full type declarations. The return type of `createRoostRenderer` is **fully inferred** from the input controller map — each property is correctly typed as an instance of the corresponding class.\n\n```typescript\nconst renderers = await createRoostRenderer(\n  { ApiController, UserController },\n  { ipcRenderer }\n)\n\n// TypeScript knows these types:\nrenderers.ApiController.greet(name: string): Promise<string>\nrenderers.UserController.list(): Promise<string[]>\n```\n\nThe `ComponentType` constraint ensures only class constructors (not plain objects or primitives) can be passed as controllers.\n\n## Main Process Integration\n\nThis package handles the **renderer side** of the IPC bridge. The main process must:\n\n1. Create a Roost app with the same controllers.\n2. Register an `ipcMain.handle()` listener on the same channel.\n3. Call `roost.invoke(path, ...args)` and return the result.\n\nSee the [Roost framework documentation](https://github.com/canlooks/roost) for details on main-process setup, including the `@canlooks/roost-electron` package which provides main-process IPC handling out of the box.\n\n## License\n\nMIT © [C.CanLiang](https://github.com/canlooks)\n","readmeFilename":"README.md","_rev":"1-b73843ff0c48df41e3c2d1a6bfd1125c"}