ee-core 5.0.0-beta.8 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Enterprise-grade Electron application framework core — TypeScript edition with
4
4
 
5
5
  ## Overview
6
6
 
7
- `ee-core` provides the runtime foundation for [electron-egg](https://github.com/wallace5303/electron-egg) applications. It handles the complete lifecycle from boot to shutdown: configuration loading, controller registration, IPC communication, window management, logging, storage, background jobs, and cross-process coordination.
7
+ `ee-core` is the runtime foundation for [electron-egg](https://github.com/wallace5303/electron-egg) applications. It handles the complete lifecycle from boot to shutdown: configuration loading, controller registration, IPC communication, window management, logging, storage, background jobs, and cross-process coordination.
8
8
 
9
9
  The package outputs dual module formats (CJS + ESM) via conditional exports in `package.json`, making it compatible with both `require()` and `import` environments.
10
10
 
@@ -19,13 +19,13 @@ npm install ee-core
19
19
  ee-core exposes fine-grained subpath modules. Each can be imported individually:
20
20
 
21
21
  ```ts
22
- // Main entry (boot, app, config, etc.)
22
+ // Main entry (boot, app, config, events, etc.)
23
23
  import { ElectronEgg } from 'ee-core';
24
24
 
25
25
  // Specific submodules
26
26
  import { ControllerLoader } from 'ee-core/controller';
27
27
  import { Cross, cross } from 'ee-core/cross';
28
- import { extend } from 'ee-core/utils';
28
+ import { extend, is } from 'ee-core/utils';
29
29
  import { SqliteStorage } from 'ee-core/storage';
30
30
  import { ChildJob, ChildPoolJob, LoadBalancer } from 'ee-core/jobs';
31
31
  import type { Config, DevConfig } from 'ee-core/types';
@@ -35,51 +35,88 @@ Available subpaths:
35
35
 
36
36
  | Subpath | Description |
37
37
  |---|---|
38
- | `ee-core` | Main entry — boot, application, config, events |
39
- | `ee-core/app` | Application lifecycle and event bus |
38
+ | `ee-core` | Main entry — boot, application, config, events, all modules |
39
+ | `ee-core/app` | Application lifecycle, event bus, directory init |
40
40
  | `ee-core/config` | Configuration loading and merging |
41
+ | `ee-core/const` | IPC channel and event constants |
41
42
  | `ee-core/controller` | Controller loader and registry |
42
- | `ee-core/core` | FileLoader and core utilities |
43
- | `ee-core/cross` | IPC communication (socket.io, HTTP, Electron IPC) |
43
+ | `ee-core/core` | FileLoader, Timing, core utilities |
44
+ | `ee-core/cross` | External process management (Go/Python backends) |
44
45
  | `ee-core/electron` | Electron app/window management |
45
- | `ee-core/exception` | Exception handling |
46
- | `ee-core/html` | HTML static resource path utility |
46
+ | `ee-core/exception` | Global exception handling (uncaught/unhandled) |
47
+ | `ee-core/html` | Static HTML page path utility (failure pages) |
47
48
  | `ee-core/jobs` | Background jobs (ChildJob, ChildPoolJob, LoadBalancer) |
48
49
  | `ee-core/loader` | File/module loading utilities |
49
- | `ee-core/log` | Pino-based logging (pino-roll + pino-pretty) |
50
- | `ee-core/message` | IPC message routing |
51
- | `ee-core/ps` | Process/environment utilities (paths, env detection) |
52
- | `ee-core/socket` | Socket.IO, HTTP, IPC servers |
50
+ | `ee-core/log` | Pino logging (pino-roll + pino-pretty) |
51
+ | `ee-core/message` | Child process IPC message routing |
52
+ | `ee-core/ps` | Process state, environment detection, path utilities |
53
+ | `ee-core/socket` | Socket.IO, HTTP (Koa), IPC servers |
53
54
  | `ee-core/storage` | SQLite storage (better-sqlite3) |
54
55
  | `ee-core/types` | TypeScript type definitions |
55
- | `ee-core/utils` | extend, is, json, helper, pargv, port |
56
+ | `ee-core/utils` | extend, is, json, wrap, helper, pargv, port, ip |
56
57
 
57
58
  ## Architecture
58
59
 
59
60
  ### Lifecycle
60
61
 
61
- Boot loadConfig loadLog loadDir → loadController → loadSocket → loadElectron
62
+ The `ElectronEgg` startup sequence is strictly ordered:
63
+
64
+ ```
65
+ init() → loadException → loadConfig → loadDir → loadLog
66
+ run() → loadController → loadSocket → emitLifecycle(Ready) → loadElectron
67
+ ```
68
+
69
+ Exception handling must register first to catch subsequent errors; config is the foundation for all modules; directories must exist before writing logs; controllers precede communication services; Ready fires after core modules are loaded; Electron starts last.
70
+
71
+ ### Lifecycle Events
72
+
73
+ Five framework milestones managed by `EventBus`:
74
+
75
+ | Event | When |
76
+ |---|---|
77
+ | `Ready` | Core modules loaded, before Electron |
78
+ | `ElectronAppReady` | `app.whenReady()` completed |
79
+ | `WindowReady` | BrowserWindow created |
80
+ | `BeforeClose` | Window close triggered |
81
+ | `Preload` | Preload scripts executing |
82
+
83
+ Register handlers via `app.register(eventName, handler)` or custom events via `eventBus.on()/emit()`.
62
84
 
63
85
  ### Controller Loading Pipeline
64
86
 
65
- - **Build time** (ee-bin): `controllerRegistryPlugin` scans `electron/controller/`, generates `ee-core:controller-registry` virtual module with lazy getters.
66
- - **Runtime** (ee-core): If `globalThis.__EE_CONTROLLER_REGISTRY__` exists (bundle mode), loads from registry. Otherwise falls back to filesystem scanning via globby.
87
+ - **Build time** (ee-bin): `bundleRegistryPlugin` scans `electron/controller/`, computes property paths (`defaultCamelize` with `caseStyle: 'lower'`), generates `app:controller-registry` virtual module setting `global.__EE_CONTROLLER_REGISTRY__` with lazy getters (`get module() { return require(...) }`).
88
+ - **Bundle entry** (`app:bundle-entry`): loads config registry controller registry real `main.js`.
89
+ - **Runtime** (ee-core): If `globalThis.__EE_CONTROLLER_REGISTRY__` exists (bundle mode), `FileLoader.parseFromRegistry()` reads from it. Otherwise, falls back to filesystem scanning via `scanDirSync()`.
90
+
91
+ ### Configuration Loading Pipeline
92
+
93
+ Same dual-mode pattern as controllers:
94
+
95
+ - **Build time** (ee-bin): `bundleRegistryPlugin` scans `electron/config/`, generates `app:config-registry` virtual module.
96
+ - **Runtime** (ee-core): If `globalThis.__EE_CONFIG_REGISTRY__` exists, `ConfigLoader._loadConfig()` loads by filename. Otherwise, reads from filesystem using `loadFile()`.
67
97
 
68
- ### IPC Communication
98
+ Config layering (dev mode only): `config.default.js` → `config.local.js` / `config.prod.js`, merged by `ConfigLoader`.
69
99
 
70
- Main renderer via `ee-core/cross`, supporting three transport modes:
71
- - Socket.IO (bidirectional real-time)
72
- - HTTP server (RESTful)
73
- - Electron IPC (native)
100
+ ### Communication Services
74
101
 
75
- Channels follow pattern `controller/{name}/{method}`.
102
+ Three channels created in order during `loadSocket()`:
103
+
104
+ | Server | Use Case | Implementation |
105
+ |---|---|---|
106
+ | `SocketServer` | Third-party process communication (Go/Python) | Socket.IO |
107
+ | `HttpServer` | RESTful API for external HTTP clients | Koa + middleware |
108
+ | `IpcServer` | Main ↔ renderer process | `ipcMain.handle/on` + `ipcRenderer.invoke/send` |
109
+
110
+ All share `resolveControllerFn()` routing — channel pattern: `controller/{name}/{method}`.
76
111
 
77
112
  ### Window Loading Strategy
78
113
 
79
- - Remote mode Load remote URL
80
- - Dev mode → Wait for frontend dev server, show loading page first
81
- - Prod + cross takeover Wait for cross-process service
82
- - Prod (default) Load local HTML file
114
+ `loadServer()` selects strategy based on config and environment:
115
+
116
+ 1. **Remote mode** (`remote.enable=true`)Load remote URL
117
+ 2. **Dev mode** → Poll frontend dev server (`waitForUrl`), show loading page first
118
+ 3. **Prod + cross takeover** → Wait for cross-process service to be ready
119
+ 4. **Prod (default)** → Load local HTML file
83
120
 
84
121
  ## Key APIs
85
122
 
@@ -89,7 +126,10 @@ Channels follow pattern `controller/{name}/{method}`.
89
126
  import { ElectronEgg } from 'ee-core';
90
127
 
91
128
  // Start the framework
92
- const app = new ElectronEgg();
129
+ const boot = new ElectronEgg();
130
+
131
+ // Async startup (ESM)
132
+ await boot.runAsync();
93
133
  ```
94
134
 
95
135
  ### Configuration
@@ -98,32 +138,83 @@ const app = new ElectronEgg();
98
138
  import { getConfig, setConfig } from 'ee-core/config';
99
139
 
100
140
  const config = getConfig();
141
+
142
+ // Child processes can receive config without filesystem access
143
+ setConfig(config);
101
144
  ```
102
145
 
103
146
  ### Controller
104
147
 
105
148
  ```ts
106
- import { getController } from 'ee-core/controller';
149
+ import { getController, getControllers } from 'ee-core/controller';
107
150
 
151
+ // Get specific controller
108
152
  const userController = getController('user');
153
+
154
+ // Get all loaded controllers
155
+ const all = getControllers();
109
156
  ```
110
157
 
111
158
  ### Cross-Process Communication
112
159
 
113
160
  ```ts
114
161
  import { cross } from 'ee-core/cross';
162
+ import type { CrossTargetConfig } from 'ee-core';
163
+
164
+ // Run Go backend with custom config
165
+ const opt: CrossTargetConfig = {
166
+ name: 'goapp',
167
+ cmd: path.join(getExtraResourcesDir(), 'goapp'),
168
+ directory: getExtraResourcesDir(),
169
+ args: ['--port=7073'],
170
+ appExit: true,
171
+ };
172
+ const entity = await cross.run('go', opt);
173
+
174
+ // Get service URL
175
+ const url = cross.getUrl('goapp');
176
+
177
+ // Kill by name
178
+ cross.killByName('goapp');
179
+ ```
180
+
181
+ ### Communication Servers
115
182
 
116
- // Send IPC message to renderer
117
- cross.sendToRenderer('controller/user/info', data);
183
+ ```ts
184
+ import { getSocketServer, getHttpServer, getIpcServer } from 'ee-core/socket';
185
+
186
+ // All getters return non-null instances after Ready event
187
+ const socket = getSocketServer();
188
+ const http = getHttpServer();
189
+ const ipc = getIpcServer();
190
+ ```
191
+
192
+ ### Main Window
193
+
194
+ ```ts
195
+ import { getMainWindow } from 'ee-core/electron';
196
+
197
+ // Returns BrowserWindow (non-null) after WindowReady event
198
+ const win = getMainWindow();
199
+ win.setMenuBarVisibility(false);
118
200
  ```
119
201
 
120
202
  ### Jobs
121
203
 
122
204
  ```ts
123
- import { ChildJob, ChildPoolJob, LoadBalancer } from 'ee-core/jobs';
205
+ import { ChildJob, ChildPoolJob, LoadBalancer, AlgorithmType } from 'ee-core/jobs';
124
206
 
207
+ // Single task execution
125
208
  const job = new ChildJob();
126
- const result = await job.exec({ name: 'task', script: './jobs/demo.js' });
209
+ const result = await job.exec('./jobs/demo.js', { taskName: 'cleanup' });
210
+
211
+ // Process pool with load balancing
212
+ const pool = new ChildPoolJob();
213
+ pool.create(3); // Create 3 workers
214
+ const result = await pool.run('./jobs/demo.js', { taskName: 'batch' });
215
+
216
+ // 8 algorithms: polling, weights, random, specify, weightsPolling,
217
+ // weightsRandom, minimumConnection, weightsMinimumConnection
127
218
  ```
128
219
 
129
220
  ### Storage
@@ -131,28 +222,99 @@ const result = await job.exec({ name: 'task', script: './jobs/demo.js' });
131
222
  ```ts
132
223
  import { SqliteStorage } from 'ee-core/storage';
133
224
 
134
- const db = new SqliteStorage('./data/app.db');
135
- db.prepare('CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, name TEXT)').run();
225
+ // Four modes: memory, onlyName, relative, absolute
226
+ const db = new SqliteStorage({ mode: 'onlyName', name: 'app' });
136
227
  ```
137
228
 
138
229
  ### Logging
139
230
 
140
231
  ```ts
141
- import { coreLogger, getLoggers } from 'ee-core/log';
232
+ import { logger, coreLogger } from 'ee-core/log';
233
+
234
+ // Supports three call styles: pino standard, pino printf, concatenation
235
+ logger.info('Application started');
236
+ logger.info('User %s logged in', 'admin');
237
+ logger.info('data:', { id: 1, name: 'test' });
142
238
 
143
- coreLogger.info('Application started');
239
+ // Log files: ee.log, ee-core.log, ee-error.log
240
+ // Rotation: daily/hourly via pino-roll
241
+ // Dev mode: pino-pretty for terminal output
144
242
  ```
145
243
 
146
244
  ### Process/Environment
147
245
 
148
246
  ```ts
149
- import { isDev, getBaseDir, env } from 'ee-core/ps';
247
+ import { isDev, isProd, getBaseDir, getDataDir, getLogDir } from 'ee-core/ps';
248
+ import { is } from 'ee-core/utils';
150
249
 
151
250
  if (isDev()) {
152
251
  // Development-specific logic
153
252
  }
253
+
254
+ // Platform detection
255
+ if (is.macOS()) { /* macOS */ }
256
+ if (is.windows()) { /* Windows */ }
257
+ if (is.linux()) { /* Linux */ }
258
+ if (is.openharmony()) { /* OpenHarmony */ }
259
+
260
+ // Path utilities
261
+ const baseDir = getBaseDir();
262
+ const dataDir = getDataDir();
263
+ const logDir = getLogDir();
264
+ const extraRes = getExtraResourcesDir();
265
+ ```
266
+
267
+ ### Event Bus
268
+
269
+ ```ts
270
+ import { eventBus, BeforeClose } from 'ee-core/app';
271
+
272
+ // Register lifecycle hook
273
+ app.register(BeforeClose, async () => {
274
+ // Cleanup before app closes
275
+ });
276
+
277
+ // Custom events
278
+ eventBus.on('myEvent', (data) => { /* handle */ });
279
+ eventBus.emit('myEvent', { key: 'value' });
280
+ ```
281
+
282
+ ### Exception Handling
283
+
284
+ ```ts
285
+ // Auto-registered during boot. Controls exit behavior:
286
+ // exception.mainExit / childExit / rendererExit
287
+ // Errors logged via coreLogger, dev mode sends to terminal
288
+ ```
289
+
290
+ ### Utility Functions
291
+
292
+ ```ts
293
+ import { extend, strictParse, getPort, parseArgv, sleep } from 'ee-core/utils';
294
+
295
+ // Deep merge with prototype pollution guard
296
+ const merged = extend(true, {}, defaults, userConfig);
297
+
298
+ // Port allocation with two-level locking
299
+ const port = await getPort({ port: 7070 });
300
+
301
+ // CLI argument parser
302
+ const args = parseArgv(process.argv);
303
+
304
+ // Sync sleep (Atomics.wait-based, no subprocess)
305
+ sleep(1000);
154
306
  ```
155
307
 
308
+ ## TypeScript Types
309
+
310
+ All types are exported from `ee-core/types`:
311
+
312
+ ```ts
313
+ import type { Config, CrossTargetConfig, DevConfig, AppInfo } from 'ee-core/types';
314
+ ```
315
+
316
+ Key type interfaces: `ElectronEggOptions`, `Config`, `AppInfo`, `CrossTargetConfig`, `CrossRunOptions`, `LoggerConfig`, `ExceptionConfig`, `FileLoaderOptions`, `LoadBalancerOptions`, `JobProcessOptions`, `MessageData`.
317
+
156
318
  ## Build
157
319
 
158
320
  ```bash
@@ -161,10 +323,21 @@ npm run build:cjs # Build CommonJS only
161
323
  npm run build:esm # Build ESM only
162
324
  npm run typecheck # TypeScript type checking
163
325
  npm run test # Run vitest
326
+ npm run test:watch # Run vitest in watch mode
164
327
  ```
165
328
 
166
329
  The `exports` map in `package.json` is auto-generated by `scripts/gen-exports.js`. Do not edit manually — run `npm run gen-exports` after adding new subpath modules.
167
330
 
331
+ ## Debug Logging
332
+
333
+ ee-core uses the `debug` library for verbose namespace-scoped logging. Enable to see runtime internals (config merging, controller loading, module resolution):
334
+
335
+ ```bash
336
+ DEBUG=ee-* node your-app # All ee-* namespaces
337
+ DEBUG=ee-core:config:* node your-app # Config subsystem only
338
+ DEBUG=ee-core:controller:* node your-app # Controller loading only
339
+ ```
340
+
168
341
  ## Requirements
169
342
 
170
343
  - Node.js >= 20.19.0
@@ -174,5 +347,6 @@ The `exports` map in `package.json` is auto-generated by `scripts/gen-exports.js
174
347
  ## Notes
175
348
 
176
349
  - ESM imports in ee-core source must include `.js` extensions (required by `module: NodeNext`)
177
- - ee-core is NOT bundled into the Electron main process bundle — it's an esbuild external loaded from `node_modules` at runtime
178
- - The framework uses the `debug` library for verbose logging. Enable with `DEBUG=ee-*` environment variable
350
+ - ee-core is NOT bundled into the Electron main process bundle — it's an esbuild external loaded from `node_modules` at runtime. This allows `child_process.fork()` to find `app.js` as a real disk file
351
+ - `getMainWindow()`, `getSocketServer()`, `getHttpServer()`, `getIpcServer()` return non-null instances. If called before the corresponding lifecycle event, they throw an Error indicating a lifecycle ordering bug
352
+ - The `exports` map is auto-generated — run `npm run gen-exports` after adding new modules
@@ -55,7 +55,7 @@ class JobProcess {
55
55
  processOptions: {
56
56
  cwd,
57
57
  env: (0, index_js_2.allEnv)(),
58
- stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
58
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc']
59
59
  },
60
60
  };
61
61
  const options = (0, extend_js_1.extend)(true, defaultOptions, opt);
@@ -55,7 +55,7 @@ class JobProcess {
55
55
  processOptions: {
56
56
  cwd,
57
57
  env: (0, index_js_2.allEnv)(),
58
- stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
58
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc']
59
59
  },
60
60
  };
61
61
  const options = (0, extend_js_1.extend)(true, defaultOptions, opt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ee-core",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0",
4
4
  "description": "ee core - TypeScript edition with dual module support",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",