npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mariodebono/di-electron

v3.4.0

Published

Electron DI app bootstrapper

Readme

@mariodebono/di-electron

Electron runtime support for applications built on @mariodebono/di.

This package bootstraps an Electron main process, manages the main window, registers controller-based IPC handlers, injects the preload bridge, and coordinates lifecycle hooks for startup and shutdown.

Use @mariodebono/di-electron for main-process code and @mariodebono/di-electron/renderer for the typed renderer bridge.

Install

npm install @mariodebono/di-electron @mariodebono/di electron reflect-metadata

Requirements

  • Node.js 24 or newer
  • An ESM-capable Electron main-process build using Electron ^42, ^43, or ^44
  • TypeScript legacy decorators enabled when using decorator APIs
  • reflect-metadata loaded before decorated classes are instantiated
  • @mariodebono/di and electron as peer dependencies

The package is developed against Electron 44.4.5, but applications can continue using any supported peer version. Electron 44 requires macOS 13 or newer and no longer supports 32-bit Windows or Linux armv7l.

This package publishes ESM .mjs files, .d.mts declarations, and a CommonJS preload bundle used internally by the window manager. CommonJS require() is not a supported public entry point.

TypeScript Setup

Use the same decorator setup required by @mariodebono/di:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Load reflect-metadata once in the Electron main-process entry before importing decorated application modules.

What It Provides

  • createElectronApplication() for DI-backed Electron bootstrap
  • ElectronModule with Electron-specific services
  • WindowManagerService for main-window tracking and additional windows
  • ElectronAppService as a thin injectable facade over Electron app APIs
  • decorator-driven app-ready, app-launch, and window lifecycle hooks
  • controller-based IPC registration through BridgeController() and typed handle decorators
  • IpcError helpers for renderer-safe error payloads
  • ElectronLogger integration through the built-in logger adapter

Quick Start

Main-process entry:

import "reflect-metadata";
import { Module } from "@mariodebono/di";
import {
    BridgeController,
    createElectronApplication,
    createIpcHandleTyped,
    ElectronAppService,
} from "@mariodebono/di-electron";

type AppBridge = {
    getLocale(): Promise<string>;
};

const AppBridgeHandle = createIpcHandleTyped<AppBridge>();

@BridgeController({ namespace: "app" })
class AppBridgeController {
    constructor(private readonly electronApp: ElectronAppService) {}

    @AppBridgeHandle("getLocale")
    getLocale(): string {
        return this.electronApp.getLocale();
    }
}

@Module({
    providers: [AppBridgeController],
})
class AppModule {}

const result = await createElectronApplication(AppModule, {
    appName: "My App",
    desktopName: "com.example.MyApp.desktop",
    mainWindowOptions: {
        url: "http://localhost:5173",
        width: 1280,
        height: 800,
    },
});

if (result.status === "started") {
    console.log("Electron app started");
}

Renderer code:

import { createRendererBridge } from "@mariodebono/di-electron/renderer";

type AppBridge = {
    getLocale(): Promise<string>;
};

const api = createRendererBridge<{ app: AppBridge }>();
const locale = await api.app.getLocale();

The renderer bridge uses the preload transport that @mariodebono/di-electron injects into windows created by WindowManagerService.

Bootstrap Model

createElectronApplication() wraps your entry module in ElectronModule, creates the DI application, and wires the result to Electron.

At runtime it:

  • sets the configured internal Electron application name and Linux desktop identity
  • creates the DI application
  • registers all BridgeController() handlers with ipcMain.handle()
  • collects app-ready, app-launch, and window lifecycle handlers
  • installs a minimal default macOS app menu
  • waits for app.whenReady()
  • runs @AppReady({ order: AppReadyOrder.BeforeWindow })
  • creates the main window through WindowManagerService
  • runs @AppReady({ order: AppReadyOrder.AfterWindow })
  • dispatches the initial @OnAppLaunch() callbacks
  • attaches main-window close, focus, blur, show, and app-quit listeners

If instanceMode: "single" is enabled and Electron cannot acquire the single-instance lock, bootstrap exits early and returns { status: "redirected" }. Otherwise it returns { status: "started", application }.

Application Options

createElectronApplication() accepts the normal @mariodebono/di application options plus Electron-specific settings.

  • appName?: string sets Electron's internal application name before bootstrap begins; it does not configure OS-level application metadata
  • desktopName?: string sets the Linux .desktop filename before bootstrap so the XDG application ID and WM_CLASS can match the installed desktop entry; it is ignored on other platforms
  • instanceMode?: "multi" | "single" controls whether a second app instance is allowed
  • hideOnClose?: boolean hides the main window instead of quitting when it is closed
  • mainWindowOptions?: CreateWindowOptions configures the main BrowserWindow
  • ipcEventValidator?: (event, channel) => void | Promise<void> applies additional validation after the package sender checks pass
  • loggerOptions?: ElectronLoggerOptions configures the built-in electron-log adapter

Defaults:

  • instanceMode defaults to "multi"
  • hideOnClose defaults to false
  • the main window defaults to url: "/" and startMode: "normal"

Window Management

WindowManagerService is the main window abstraction exposed by the package.

It supports:

  • createWindow(options) for additional windows
  • createMainWindow(options) for the singleton main window
  • getMainWindow() to read the tracked main window
  • revealMainWindow() to restore, show, and focus the main window
  • closeAll() to close all tracked windows

CreateWindowOptions extends Electron's BrowserWindowConstructorOptions with:

  • url?: string
  • basePath?: string | (() => string | Promise<string>)
  • startMode?: "normal" | "minimized" | "hidden"

The package owns webPreferences.preload and injects its preload bridge automatically for every window.

URL resolution rules:

  • absolute http:, https:, and file: URLs are loaded with loadURL()
  • relative URLs are resolved against basePath when provided
  • otherwise the value is passed to loadFile()

That makes the package suitable for both development servers and packaged files.

Lifecycle Decorators

Lifecycle decorators also mark the class as injectable when needed.

@AppReady()

Runs after app.whenReady().

  • order: AppReadyOrder.BeforeWindow runs before the main window is created
  • order: AppReadyOrder.AfterWindow runs after the main window is created
  • priority sorts handlers in ascending order

Handlers are awaited sequentially. Errors are logged and do not stop the remaining handlers from running.

@OnAppLaunch()

Runs when the application launches.

The handler receives:

type AppLaunchContext = {
    kind: "initial" | "second-instance";
    argv: string[];
    workingDirectory?: string;
    additionalData?: unknown;
};

In instanceMode: "single", second-instance events are queued until initial startup has finished, then replayed in order.

Window and App Hooks

Available decorators:

  • @OnAppQuit()
  • @OnMainWindowClose()
  • @OnMainWindowFocus()
  • @OnMainWindowBlur()
  • @OnMainWindowShow()

Each accepts:

{
    priority?: number;
    order?: LifecycleHookOrder; // "before" | "after"
}

These hooks are also awaited sequentially and sorted by ascending priority.

Close and Quit Guards

Use @BeforeAppQuit() to check permission before normal application quit, and @BeforeMainWindowClose() to check permission before closing or hiding the managed main window. Both accept { priority?: number } and return boolean or Promise<boolean>. Return true to allow the action or false to cancel it.

import { Inject, Injectable } from "@mariodebono/di";
import {
    BeforeAppQuit,
    BeforeMainWindowClose,
} from "@mariodebono/di-electron";

@Injectable()
class OperationsService {
    busy = false;
}

@Injectable()
class ExitPolicy {
    constructor(@Inject(OperationsService) private readonly operations: OperationsService) {}

    @BeforeAppQuit()
    @BeforeMainWindowClose()
    canExit(): boolean {
        return !this.operations.busy;
    }
}

Register both providers in your module. Your application owns the busy state and any message or confirmation dialog. Set the state before starting critical work, and clear it in finally when the work finishes or fails. An async guard can await an application-owned confirmation dialog before returning its decision.

Guards run sequentially in ascending priority and then discovery order. The first false cancels the action. A thrown error, rejected promise or non-boolean result is logged and also cancels it. Existing lifecycle hooks keep their notification semantics; their return values do not cancel anything.

The package prevents the native action synchronously while guards are pending. Repeated requests share the pending check. Quit hooks run only after quit permission is granted; close hooks run only after close permission is granted. A cancelled attempt is not queued: the next request checks permission again. A guard that never settles keeps the action blocked, so guards should return promptly or wait only for a deliberate user decision.

An allowed window close retains hideOnClose behaviour. If it requests application quit, quit guards must also approve. During an approved application quit, main-window close guards are skipped to avoid a second permission cycle. Register both guards when protecting work from both Close and Quit. A close guard alone does not veto Quit.

Normal menu Quit, keyboard Quit and ElectronAppService.quit() use the quit guards. Force quit, crashes, system shutdown and app.exit() cannot be reliably blocked. Updater installation needs a separate permission check before quitAndInstall(): Electron can close windows before emitting before-quit for that operation. Keep recovery mechanisms for interrupted work. See the Electron application lifecycle.

Applications without guards retain their existing close and quit behaviour.

IPC Controllers

IPC is controller-based. Classes marked with @BridgeController({ namespace }) are discovered from the DI container, then methods marked with @IpcHandleTyped() or a decorator created by createIpcHandleTyped() are registered through ipcMain.handle().

Before a controller method runs, the package verifies that the sender belongs to a window managed by WindowManagerService, that the request comes from its main frame, and that the frame remains on the configured web origin or local renderer file. Invalid senders receive a normalized electron.invalid_ipc_sender error. Use ipcEventValidator only for additional application-specific rules.

import { BridgeController, createIpcHandleTyped } from "@mariodebono/di-electron";

type ProjectsBridge = {
    list(): Promise<string[]>;
};

const ProjectsHandle = createIpcHandleTyped<ProjectsBridge>();

@BridgeController({ namespace: "projects" })
class ProjectsBridgeController {
    @ProjectsHandle("list")
    async listProjects(): Promise<string[]> {
        return ["project-a", "project-b"];
    }
}

Handler results are wrapped in a stable response shape and then unwrapped by the renderer bridge.

IPC Errors

Use IpcError when you want predictable, client-safe error payloads.

import { IpcError } from "@mariodebono/di-electron";

class ProjectNotFoundError extends IpcError {
    constructor(projectId: string) {
        super("projects.not_found", `Project ${projectId} was not found`);
    }
}

Helpers exported by the package:

  • serializeIpcError(error)
  • toClientIpcError(error)
  • isIpcError(value)
  • isSerializedIpcError(value)

Plain thrown errors are normalized into a fallback { type, message } shape.

Renderer Bridge

Use @mariodebono/di-electron/renderer in renderer code.

import { createRendererBridge } from "@mariodebono/di-electron/renderer";

const api = createRendererBridge<{ app: { getLocale(): Promise<string> } }>();
const locale = await api.app.getLocale();

The renderer bridge maps nested property access to dotted IPC channels and automatically unwraps successful responses.

The subpath also exports createRendererEvents() and getDefaultRendererTransport() for tests or specialized transports.

Use getPathForFile(file) when a drag-and-drop flow needs the native filesystem path for a browser File. The package-owned preload resolves the path through Electron's webUtils without exposing webUtils directly.

Electron Services

ElectronModule

ElectronModule provides and exports:

  • WindowManagerService
  • ElectronAppService
  • CloseBehaviorService

You usually do not import ElectronModule manually when you use createElectronApplication(), because it is already composed into the platform root module.

ElectronAppService

ElectronAppService is a thin facade over frequently used Electron APIs. It keeps Electron-specific code injectable and easy to mock in tests.

Examples include:

  • getAppPath()
  • getPreferredSystemLanguages()
  • getLocale()
  • getPath(name)
  • quit() and exit(code?)
  • setHideOnClose(value) and getHideOnClose()
  • setApplicationMenu(menu) and clearApplicationMenu()
  • openPath(path), revealPath(path), openExternal(url)
  • showMessageBox(options) and showOpenDialog(options)
  • hideDock(), showDock(), setDockIcon(icon)
  • onActivate(listener) and offActivate(listener)
  • showMenu(window, menu, x, y)
  • setName(name)
  • setTheme(symbolColor, color)

Logging

Pass loggerOptions to createElectronApplication() to use the built-in electron-log adapter.

await createElectronApplication(AppModule, {
    loggerOptions: {
        appName: "my-app",
        logFilePath: "/tmp/my-app.log",
        fileLevel: "info",
        consoleLevel: "debug",
    },
});

ElectronLoggerOptions supports:

  • appName
  • fileLevel
  • consoleLevel
  • logFilePath
  • maxSize
  • configure(logger)
  • loggerInstance

If you already have a logger implementation for @mariodebono/di, you can still pass logger directly through the base application options.

Public API

The package exports:

  • createElectronApplication() and the related bootstrap types
  • lifecycle decorators and enums
  • IPC decorators, controller helpers, and error helpers
  • ElectronModule
  • ElectronAppService
  • WindowManagerService
  • CloseBehaviorService
  • ElectronLogger and createElectronLogger()
  • IpcError, serializeIpcError(), toClientIpcError(), isIpcError(), and isSerializedIpcError()

Renderer Subpath

@mariodebono/di-electron/renderer exports:

  • createRendererBridge()
  • createRendererEvents()
  • getDefaultRendererTransport()
  • RendererIpcListener
  • RendererIpcTransport
  • RendererEventBridge

Changelog

See CHANGELOG.md.

License

This package is licensed under MPL-2.0.