Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified illustration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devisfuture/electron-modular",
"version": "1.1.12",
"version": "1.1.14",
"description": "Core module system, DI container, IPC handlers, and window utilities for Electron main process.",
"type": "module",
"main": "./dist/index.js",
Expand Down
32 changes: 32 additions & 0 deletions src/@core/bootstrap/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
/**
* @fileoverview Bootstrap entry point for initializing all application modules.
*
* This file orchestrates the module initialization process:
* 1. Validates that modules have the @RgModule decorator
* 2. Initializes each module (registers providers, imports, windows, IPC handlers)
* 3. Instantiates the module class
* 4. Resolves module dependencies
* 5. Initializes IPC handlers with window factory functions
*
* @module @core/bootstrap/bootstrap
*/

import type { RgModuleMetadata } from "../types/module-metadata.js";
import type { Constructor } from "../types/constructor.js";
import { ModuleDecoratorMissingError } from "../errors/index.js";
Expand All @@ -6,6 +19,25 @@ import { initializeModule } from "./initialize-module.js";
import { container } from "../container.js";
import { initializeIpcHandlers } from "./initialize-ipc/handlers.js";

/**
* Bootstraps an array of modules in the application.
*
* This is the main entry point for initializing the dependency injection container
* and setting up all modules. It processes each module sequentially to ensure
* proper dependency resolution order.
*
* @param modulesClass - Array of module class constructors to bootstrap
* @throws {ModuleDecoratorMissingError} If a module is missing the @RgModule decorator
*
* @example
* ```typescript
* await bootstrapModules([
* UserModule,
* ResourcesModule,
* AuthModule
* ]);
* ```
*/
export const bootstrapModules = async (
modulesClass: Constructor[],
): Promise<void> => {
Expand Down
35 changes: 35 additions & 0 deletions src/@core/bootstrap/initialize-ipc/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* @fileoverview IPC handler initialization with window factory functions.
*
* Creates window factory functions and initializes IPC handlers by calling their onInit methods.
* Each IPC handler receives a getWindow function that returns window factories keyed by hash.
*
* @module @core/bootstrap/initialize-ipc/handlers
*/

import type { BrowserWindow } from "electron";
import type { Constructor } from "../../types/constructor.js";
import type { TIpcHandlerInterface } from "../../types/ipc-handler.js";
Expand All @@ -10,6 +19,13 @@ import { createWindowWithParams } from "./window-creator.js";
import { createWindowInstance } from "./window-instance-creator.js";
import { attachWindowEventListeners } from "./window-event-listeners.js";

/**
* Creates a window factory that can instantiate BrowserWindows.
*
* @param moduleClass - The module context
* @param windowMetadata - Window metadata including options and hash
* @returns Window factory with create method
*/
const createWindowFactory = (
moduleClass: Constructor,
windowMetadata: TMetadataWindow | undefined,
Expand Down Expand Up @@ -47,6 +63,12 @@ const createWindowFactory = (
};
};

/**
* Creates a getWindow function for retrieving window factories by hash.
*
* @param moduleClass - The module context
* @returns Function that returns window factories by hash
*/
const createGetWindowFunction = (moduleClass: Constructor) => {
return (name?: string): TWindowFactory => {
if (!name) {
Expand All @@ -64,6 +86,19 @@ const createGetWindowFunction = (moduleClass: Constructor) => {
};
};

/**
* Initializes all IPC handlers for a module.
*
* Process:
* 1. Creates a getWindow function for the module
* 2. Resolves each IPC handler instance from the container
* 3. Calls onInit on each handler with the getWindow function
*
* This allows IPC handlers to access window factories and create windows dynamically.
*
* @param moduleClass - The module class owning these IPC handlers
* @param metadata - Module metadata containing ipc array
*/
export const initializeIpcHandlers = async (
moduleClass: Constructor,
metadata: RgModuleMetadata,
Expand Down
43 changes: 43 additions & 0 deletions src/@core/bootstrap/initialize-ipc/window-creator.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
/**
* @fileoverview Window creation with parameter merging.
*
* Handles merging of base window metadata with runtime parameters to create BrowserWindows.
* Supports deep merging of nested configuration objects.
*
* @module @core/bootstrap/initialize-ipc/window-creator
*/

import type { BrowserWindow } from "electron";
import type { TParamsCreateWindow } from "../../control-window/types.js";
import { createWindow } from "../../control-window/create.js";

/** Plain JavaScript object type */
type TPlainObject = Record<string, unknown>;

/**
* Type guard to check if a value is a plain object.
*
* @param value - Value to check
* @returns true if value is a plain object
*/
const isPlainObject = (value: unknown): value is TPlainObject => {
return (
typeof value === "object" &&
Expand All @@ -13,6 +29,15 @@ const isPlainObject = (value: unknown): value is TPlainObject => {
);
};

/**
* Deep merges two objects recursively.
*
* Plain objects are merged recursively, other values from source override target.
*
* @param target - Target object
* @param source - Source object with values to merge
* @returns Merged object
*/
const mergeDeep = <T extends TPlainObject>(target: T, source: T): T => {
const output: TPlainObject = { ...target };

Expand All @@ -30,6 +55,24 @@ const mergeDeep = <T extends TPlainObject>(target: T, source: T): T => {
return output as T;
};

/**
* Creates a BrowserWindow with merged metadata and runtime parameters.
*
* Base metadata from @WindowManager is deep merged with optional runtime params.
* This allows dynamic window configuration while maintaining sensible defaults.
*
* @param baseMetadata - Base window metadata from decorator
* @param params - Optional runtime parameters to merge
* @returns Created BrowserWindow instance
*
* @example
* ```typescript
* const window = createWindowWithParams(
* { hash: 'window:main', options: { width: 800 } },
* { options: { height: 600 } }
* );
* ```
*/
export const createWindowWithParams = <W extends TParamsCreateWindow>(
baseMetadata: W,
params?: W,
Expand Down
67 changes: 67 additions & 0 deletions src/@core/bootstrap/initialize-ipc/window-event-listeners.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
/**
* @fileoverview Window lifecycle event listener attachment.
*
* Automatically attaches lifecycle hooks from window manager instances to BrowserWindow
* and WebContents events. Supports:
* - BrowserWindow events (onFocus, onClose, etc.)
* - WebContents events (onWebContentsDidFinishLoad, etc.)
*
* Event handlers are named with 'on' prefix and CamelCase, which are converted to kebab-case event names.
*
* @module @core/bootstrap/initialize-ipc/window-event-listeners
*/

import type { BrowserWindow, WebContents } from "electron";
import type { TWindowManagerWithHandlers } from "../../types/window-manager.js";

/** Event emitter interface (BrowserWindow or WebContents) */
type TEventEmitter = Pick<
BrowserWindow | WebContents,
"on" | "off" | "removeListener"
>;

/** Tracks attached listeners and cleanup functions for each window */
type TWindowListenerEntry = {
instance: TWindowManagerWithHandlers;
cleanup: Array<() => void>;
};

/** WeakMap to track listeners per window instance */
const windowListeners = new WeakMap<BrowserWindow, TWindowListenerEntry>();

/**
* Extracts all method names from an object's prototype chain.
*
* @param instance - Object to inspect
* @returns Array of method names
*/
const getPrototypeMethodNames = (instance: object): string[] => {
const names = new Set<string>();
let proto = Object.getPrototypeOf(instance);
Expand All @@ -25,6 +47,17 @@ const getPrototypeMethodNames = (instance: object): string[] => {
return Array.from(names);
};

/**
* Converts a method name to an Electron event name.
*
* Examples:
* - onFocus -> focus
* - onWebContentsDidFinishLoad -> did-finish-load
* - onMaximize -> maximize
*
* @param h - Handler method name
* @returns Kebab-case event name
*/
const toEventName = (h: string): string => {
const c = h.replace(/^(onWindow|onWebContents|on)/, "");
return c
Expand All @@ -33,6 +66,19 @@ const toEventName = (h: string): string => {
.toLowerCase();
};

/**
* Attaches event handlers from window manager instance to an event emitter.
*
* Handlers with 0-1 parameters receive only the BrowserWindow instance.
* Handlers with 2+ parameters receive original Electron event arguments plus the window.
*
* @param emitter - BrowserWindow or WebContents to attach listeners to
* @param win - BrowserWindow instance to pass to handlers
* @param inst - Window manager instance containing handler methods
* @param names - Method names to consider
* @param filter - Function to filter which methods to attach
* @returns Array of cleanup functions to remove listeners
*/
const attachHandlersToEmitter = (
emitter: TEventEmitter,
win: BrowserWindow,
Expand Down Expand Up @@ -60,6 +106,27 @@ const attachHandlersToEmitter = (
return cleanups;
};

/**
* Attaches all lifecycle event listeners from a window manager to a BrowserWindow.
*
* Process:
* 1. Checks if listeners are already attached to avoid duplicates
* 2. Extracts all methods starting with 'on' from window manager
* 3. Separates BrowserWindow events from WebContents events
* 4. Attaches handlers to appropriate emitters
* 5. Registers cleanup on window close
*
* @param win - BrowserWindow instance
* @param inst - Window manager instance with lifecycle hooks
*
* @example
* ```typescript
* const window = new BrowserWindow();
* const manager = new MyWindowManager();
* attachWindowEventListeners(window, manager);
* // Now manager.onFocus() will be called when window focuses
* ```
*/
export const attachWindowEventListeners = (
win: BrowserWindow,
inst: TWindowManagerWithHandlers,
Expand Down
26 changes: 26 additions & 0 deletions src/@core/bootstrap/initialize-ipc/window-instance-creator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
/**
* @fileoverview Window manager instance creation.
*
* Instantiates window manager classes with their dependencies resolved from the container.
* Uses reflection to discover constructor dependencies automatically.
*
* @module @core/bootstrap/initialize-ipc/window-instance-creator
*/

import type { Constructor } from "../../types/constructor.js";
import type { TWindowManagerWithHandlers } from "../../types/window-manager.js";
import { container } from "../../container.js";
import { getDependencyTokens } from "../../utils/dependency-tokens.js";

/**
* Creates an instance of a window manager class with resolved dependencies.
*
* Process:
* 1. Extracts constructor parameter types using reflection
* 2. Resolves each dependency from the container
* 3. Instantiates the window manager with resolved dependencies
*
* @param moduleClass - The module context for dependency resolution
* @param windowClass - The window manager class constructor
* @returns Instantiated window manager or undefined if class is invalid
*
* @example
* ```typescript
* const instance = await createWindowInstance(UserModule, UserWindow);
* ```
*/
export const createWindowInstance = async <
T extends TWindowManagerWithHandlers,
>(
Expand Down
27 changes: 27 additions & 0 deletions src/@core/bootstrap/initialize-module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* @fileoverview Module initialization coordinator.
*
* Orchestrates the registration of all module components:
* - Providers (services and factories)
* - Imported modules
* - Window managers
* - IPC handlers
*
* @module @core/bootstrap/initialize-module
*/

import type { RgModuleMetadata } from "../types/module-metadata.js";
import type { Constructor } from "../types/constructor.js";
import { container } from "../container.js";
Expand All @@ -6,6 +18,21 @@ import { registerImports } from "./register-imports.js";
import { registerWindows } from "./register-windows.js";
import { registerIpcHandlers } from "./register-ipc-handlers.js";

/**
* Initializes a module by registering all its components in the container.
*
* Process:
* 1. Adds module to container with metadata
* 2. Registers all providers
* 3. Recursively initializes imported modules
* 4. Registers window managers
* 5. Registers IPC handlers
*
* All registration steps run in parallel for performance.
*
* @param moduleClass - The module class constructor
* @param metadata - Module metadata from @RgModule decorator
*/
export const initializeModule = async (
moduleClass: Constructor,
metadata: RgModuleMetadata,
Expand Down
26 changes: 26 additions & 0 deletions src/@core/bootstrap/instantiate-module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
/**
* @fileoverview Module instantiation logic.
*
* Handles the creation of module instances with automatic dependency resolution.
* Uses reflection to discover constructor dependencies and resolves them from the container.
*
* @module @core/bootstrap/instantiate-module
*/

import type { Constructor } from "../types/constructor.js";
import { container } from "../container.js";
import { getDependencyTokens } from "../utils/dependency-tokens.js";

/**
* Instantiates a module class with its resolved dependencies.
*
* Process:
* 1. Extracts constructor parameter types using reflection
* 2. Resolves each dependency from the container
* 3. Creates a new instance with resolved dependencies
* 4. Registers the instance in the global container
*
* @param moduleClass - The module class constructor to instantiate
* @returns The instantiated module instance
*
* @example
* ```typescript
* const instance = await instantiateModule(UserModule);
* ```
*/
export const instantiateModule = async (
moduleClass: Constructor,
): Promise<unknown> => {
Expand Down
Loading