@canlooks/roost-electron 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C.CanLiang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @canlooks/roost-electron
2
+
3
+ Electron main process plugin for the [Roost](https://github.com/canlooks/roost) microservice framework. Bridges Electron's IPC (Inter-Process Communication) from renderer processes directly to Roost service controllers running in the main process.
4
+
5
+ ## Overview
6
+
7
+ `@canlooks/roost-electron` is a lightweight plugin that registers an [`ipcMain.handle()`](https://www.electronjs.org/docs/latest/api/ipc-main#ipcmainhandlechannel-listener) listener on a configurable channel. When a renderer process sends an IPC invoke call, the plugin forwards the invocation key and arguments to `app.invoke()`, routing the request to the matching Roost controller action.
8
+
9
+ Paired with [`@canlooks/roost-electron-renderer`](https://www.npmjs.com/package/@canlooks/roost-electron-renderer) on the renderer side, this enables seamless RPC-style communication where renderer-side controller method calls are transparently proxied via Electron IPC to the main process.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @canlooks/roost-electron
15
+ ```
16
+
17
+ **Peer dependencies:**
18
+ - `@canlooks/roost` (core framework)
19
+ - `electron` (main process runtime)
20
+
21
+ ## Quick Start
22
+
23
+ ### Main Process
24
+
25
+ ```typescript
26
+ import { app, BrowserWindow } from 'electron'
27
+ import Roost from '@canlooks/roost'
28
+ import { ElectronMainPlugin } from '@canlooks/roost-electron'
29
+ import { MyService } from './services/MyService'
30
+
31
+ async function main() {
32
+ const roost = await Roost.create({
33
+ named: { MyService },
34
+ plugins: [
35
+ ElectronMainPlugin()
36
+ ]
37
+ })
38
+
39
+ const win = new BrowserWindow({
40
+ webPreferences: {
41
+ preload: path.join(__dirname, 'preload.js')
42
+ }
43
+ })
44
+ win.loadFile('index.html')
45
+ }
46
+
47
+ app.whenReady().then(main)
48
+ ```
49
+
50
+ ### Renderer Process (with `@canlooks/roost-electron-renderer`)
51
+
52
+ ```typescript
53
+ import { contextBridge, ipcRenderer } from 'electron'
54
+ import { createRoostRenderer } from '@canlooks/roost-electron-renderer'
55
+ import { MyService } from '../services/MyService'
56
+
57
+ contextBridge.exposeInMainWorld('roost', {
58
+ services: await createRoostRenderer(
59
+ { MyService },
60
+ { ipcRenderer }
61
+ )
62
+ })
63
+ ```
64
+
65
+ Then, in the renderer page:
66
+
67
+ ```typescript
68
+ // MyService methods are transparently proxied to the main process
69
+ const result = await window.roost.services.MyService.doSomething(args)
70
+ ```
71
+
72
+ ## API Reference
73
+
74
+ ### `ElectronMainPlugin(options?)`
75
+
76
+ Factory function that creates a Roost `Plugin` object for the Electron main process.
77
+
78
+ ```typescript
79
+ function ElectronMainPlugin(options?: ElectronMainPluginOptions): Plugin
80
+ ```
81
+
82
+ #### `ElectronMainPluginOptions`
83
+
84
+ | Property | Type | Default | Description |
85
+ | --------- | -------- | ----------------------------- | ---------------------------------------------------------- |
86
+ | `channel` | `string` | `"@canlooks/roost-electron"` | The IPC channel name used for `ipcMain.handle()`. Customize this to avoid conflicts with other IPC handlers. |
87
+
88
+ #### Return Value
89
+
90
+ Returns a `Plugin` object conforming to the Roost `Plugin` interface:
91
+
92
+ ```typescript
93
+ {
94
+ name: 'electron-main',
95
+ onStaticInjected: (app: Roost) => void
96
+ }
97
+ ```
98
+
99
+ ### `registerIpcMain(app, options?)`
100
+
101
+ Low-level function called internally by the plugin. Registers the `ipcMain.handle()` listener directly.
102
+
103
+ ```typescript
104
+ function registerIpcMain(app: Roost, options?: ElectronMainPluginOptions): void
105
+ ```
106
+
107
+ > This is exported for advanced use cases where you need to control registration timing manually. In most cases, use `ElectronMainPlugin()` instead.
108
+
109
+ ## How It Works
110
+
111
+ ### Architecture
112
+
113
+ ```
114
+ ┌─────────────────────────────────────────────────────────┐
115
+ │ Renderer Process │
116
+ │ ┌───────────────────────────────────────────────────┐ │
117
+ │ │ createRoostRenderer({ MyService }, { ipcRenderer })│ │
118
+ │ │ → Rewrites MyService methods to call │ │
119
+ │ │ ipcRenderer.invoke(channel, key, ...args) │ │
120
+ │ └───────────────────────┬───────────────────────────┘ │
121
+ └──────────────────────────┼──────────────────────────────┘
122
+ │ Electron IPC
123
+ ┌──────────────────────────┼──────────────────────────────┐
124
+ │ Main Process │ │
125
+ │ ┌───────────────────────▼───────────────────────────┐ │
126
+ │ │ ElectronMainPlugin │ │
127
+ │ │ → ipcMain.handle(channel, (e, key, ...args) => { │ │
128
+ │ │ return app.invoke(key, ...args) │ │
129
+ │ │ }) │ │
130
+ │ └───────────────────────┬───────────────────────────┘ │
131
+ │ │ │
132
+ │ ┌───────────────────────▼───────────────────────────┐ │
133
+ │ │ Roost App │ │
134
+ │ │ → app.invoke(key, ...args) │ │
135
+ │ │ → Route to matching @Controller/@Action │ │
136
+ │ │ → Execute, return result │ │
137
+ │ └───────────────────────────────────────────────────┘ │
138
+ └─────────────────────────────────────────────────────────┘
139
+ ```
140
+
141
+ ### Lifecycle
142
+
143
+ The plugin hooks into the `onStaticInjected` lifecycle event of Roost:
144
+
145
+ 1. **`Roost.create()`** is called with the plugin in the `plugins` array.
146
+ 2. Roost registers all modules and performs dependency injection.
147
+ 3. **`onStaticInjected`** fires — the plugin registers `ipcMain.handle()` on the configured channel.
148
+ 4. The main process is now ready to receive IPC calls from renderer processes.
149
+
150
+ ### Invocation Flow
151
+
152
+ When a renderer calls `window.roost.services.MyService.doSomething(arg)`:
153
+
154
+ 1. `@canlooks/roost-electron-renderer` rewrites the method to call `ipcRenderer.invoke('@canlooks/roost-electron', 'path/to/action', arg)`.
155
+ 2. The IPC message arrives in the main process.
156
+ 3. The `ipcMain.handle()` listener receives `(event, key, arg)`.
157
+ 4. It calls `app.invoke(key, arg)` on the Roost instance.
158
+ 5. Roost's `Invoker` matches the key against registered controllers and actions (path-based, pattern-based, or regex-based routing).
159
+ 6. The matched controller method executes and returns a result.
160
+ 7. The result is sent back through the IPC channel to the renderer.
161
+
162
+ ## Custom Channel
163
+
164
+ If the default channel name conflicts with other IPC handlers in your application, provide a custom channel:
165
+
166
+ ```typescript
167
+ ElectronMainPlugin({ channel: 'my-app:rpc' })
168
+ ```
169
+
170
+ Make sure to use the same channel name in the renderer side:
171
+
172
+ ```typescript
173
+ createRoostRenderer(
174
+ { MyService },
175
+ { ipcRenderer, channel: 'my-app:rpc' }
176
+ )
177
+ ```
178
+
179
+ ## Project Structure
180
+
181
+ ```
182
+ packages/electron/
183
+ ├── src/
184
+ │ ├── index.ts # Plugin factory + type exports
185
+ │ └── registerIpcMain.ts # IPC handler registration
186
+ ├── dist/
187
+ │ ├── cjs/ # CommonJS build output
188
+ │ └── esm/ # ES Module build output
189
+ ├── test/
190
+ ├── package.json
191
+ ├── tsconfig.json
192
+ ├── LICENSE
193
+ └── README.md
194
+ ```
195
+
196
+ ## TypeScript
197
+
198
+ The package is written in TypeScript and ships with declaration files. TypeScript 6.0+ and `strict` mode are used during development.
199
+
200
+ ### Exports
201
+
202
+ ```typescript
203
+ // Factory function
204
+ export function ElectronMainPlugin(options?: ElectronMainPluginOptions): Plugin
205
+
206
+ // Options type
207
+ export type ElectronMainPluginOptions = {
208
+ channel?: string
209
+ }
210
+
211
+ // Low-level registration function
212
+ export function registerIpcMain(app: Roost, options?: ElectronMainPluginOptions): void
213
+ ```
214
+
215
+ ## Related Packages
216
+
217
+ | Package | Description |
218
+ | ------- | ----------- |
219
+ | [`@canlooks/roost`](https://www.npmjs.com/package/@canlooks/roost) | Core microservice framework |
220
+ | [`@canlooks/roost-electron-renderer`](https://www.npmjs.com/package/@canlooks/roost-electron-renderer) | Renderer-side companion — creates proxy controllers that communicate via IPC |
221
+
222
+ ## License
223
+
224
+ MIT © [C.CanLiang](https://github.com/canlooks)
@@ -0,0 +1,5 @@
1
+ import { Plugin } from '@canlooks/roost';
2
+ export type ElectronMainPluginOptions = {
3
+ channel?: string;
4
+ };
5
+ export declare function ElectronMainPlugin(options?: ElectronMainPluginOptions): Plugin;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ElectronMainPlugin = ElectronMainPlugin;
4
+ const registerIpcMain_1 = require("./registerIpcMain");
5
+ function ElectronMainPlugin(options) {
6
+ return {
7
+ name: 'electron-main',
8
+ onStaticInjected: app => (0, registerIpcMain_1.registerIpcMain)(app, options)
9
+ };
10
+ }
@@ -0,0 +1,3 @@
1
+ import { ElectronMainPluginOptions } from './index';
2
+ import Roost from '@canlooks/roost';
3
+ export declare function registerIpcMain(app: Roost, options?: ElectronMainPluginOptions): void;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerIpcMain = registerIpcMain;
4
+ const electron_1 = require("electron");
5
+ function registerIpcMain(app, options) {
6
+ const channel = options?.channel || '@canlooks/roost-electron';
7
+ electron_1.ipcMain.handle(channel, (e, key, ...args) => {
8
+ return app.invoke(key, ...args);
9
+ });
10
+ }
@@ -0,0 +1,5 @@
1
+ import { Plugin } from '@canlooks/roost';
2
+ export type ElectronMainPluginOptions = {
3
+ channel?: string;
4
+ };
5
+ export declare function ElectronMainPlugin(options?: ElectronMainPluginOptions): Plugin;
@@ -0,0 +1,7 @@
1
+ import { registerIpcMain } from './registerIpcMain.js';
2
+ export function ElectronMainPlugin(options) {
3
+ return {
4
+ name: 'electron-main',
5
+ onStaticInjected: app => registerIpcMain(app, options)
6
+ };
7
+ }
@@ -0,0 +1,3 @@
1
+ import { ElectronMainPluginOptions } from './index.js';
2
+ import Roost from '@canlooks/roost';
3
+ export declare function registerIpcMain(app: Roost, options?: ElectronMainPluginOptions): void;
@@ -0,0 +1,7 @@
1
+ import { ipcMain } from 'electron';
2
+ export function registerIpcMain(app, options) {
3
+ const channel = options?.channel || '@canlooks/roost-electron';
4
+ ipcMain.handle(channel, (e, key, ...args) => {
5
+ return app.invoke(key, ...args);
6
+ });
7
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@canlooks/roost-electron",
3
+ "version": "0.0.1",
4
+ "author": "C.CanLiang <canlooks@gmail.com>",
5
+ "description": "A backend micro service framework",
6
+ "keywords": [
7
+ "micro service"
8
+ ],
9
+ "main": "dist/cjs/index.js",
10
+ "module": "dist/esm/index.js",
11
+ "types": "dist/esm/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/esm/index.d.ts",
15
+ "import": "./dist/esm/index.js",
16
+ "require": "./dist/cjs/index.js"
17
+ }
18
+ },
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "registry": "https://registry.npmjs.org/"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/canlooks/roost"
26
+ },
27
+ "homepage": "https://github.com/canlooks/roost",
28
+ "bugs": {
29
+ "url": "https://github.com/canlooks/roost/issues",
30
+ "email": "canlooks@gmail.com"
31
+ },
32
+ "license": "MIT",
33
+ "scripts": {
34
+ "clean": "npx shx rm -rf dist",
35
+ "build": "tsc -m esnext --outDir dist/esm & tsc -m commonjs --outDir dist/cjs",
36
+ "build:alias": "tsc-alias --outDir dist/esm",
37
+ "rebuild": "npm run clean && npm run build && npm run build:alias"
38
+ },
39
+ "dependencies": {
40
+ "@canlooks/roost": "^0.0.1",
41
+ "tslib": "^2.8.1"
42
+ },
43
+ "devDependencies": {
44
+ "@types/express": "^5.0.6",
45
+ "@types/node": "^25.9.1",
46
+ "electron": "^42.3.0",
47
+ "tsc-alias": "^1.8.17",
48
+ "typescript": "^6.0.3"
49
+ }
50
+ }