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

@enclosurejs/platform-electron

v1.1.0

Published

> [!IMPORTANT] > This package bridges `@enclosurejs/core` to Electron. It provides `ElectronBackend` (the `BackendAdapter`), a preload helper, a main-process IPC setup function, and concrete implementations for all 26 capability contracts. Your applicatio

Readme

@enclosurejs/platform-electron — Electron Backend + 26 Capability Implementations

[!IMPORTANT] This package bridges @enclosurejs/core to Electron. It provides ElectronBackend (the BackendAdapter), a preload helper, a main-process IPC setup function, and concrete implementations for all 26 capability contracts. Your application code never imports this package directly — it consumes capability tokens from @enclosurejs/core.

The Problem

Electron's API surface is split across three processes (main, renderer, preload), connected by ipcMain.handle / ipcRenderer.invoke. Wiring 26 capabilities across this boundary by hand means hundreds of IPC channels, manual serialization, and duplicated boilerplate. If you later want to switch to Tauri, every callsite that touches ipcRenderer must be rewritten.

@enclosurejs/platform-electron solves this by mapping every @enclosurejs/core capability token to an Electron implementation behind a single registration call. The renderer process talks to abstract interfaces via DI; the main process registers IPC handlers via one function call. Switching to Tauri means swapping the import — zero changes to business logic.

Architecture

┌────────────────── Renderer Process ──────────────────┐
│                                                       │
│  App code → context.use(FileSystemToken)              │
│               │                                       │
│  ElectronFileSystem.readTextFile(path)                 │
│               │                                       │
│  window.__enclosure.invoke('enclosure:fs:readTextFile')│
│               │ (contextBridge)                        │
└───────────────┼───────────────────────────────────────┘
                │ ipcRenderer.invoke
┌───────────────┼──── Main Process ─────────────────────┐
│               │                                       │
│  setupElectronMain()                                  │
│    ipcMain.handle('enclosure:fs:readTextFile', ...)   │
│      → node:fs/promises.readFile(path, 'utf-8')       │
│                                                       │
└───────────────────────────────────────────────────────┘

Three layers, each with a single responsibility:

| Layer | File | Runs in | Responsibility | | ---------------------- | -------------------------------------- | ---------------- | -------------------------------------------------------------- | | Main setup | @enclosurejs/platform-electron/main | Main process | Register all ipcMain.handle handlers | | Preload | @enclosurejs/platform-electron/preload | Preload script | Expose window.__enclosure bridge via contextBridge | | Backend + Services | @enclosurejs/platform-electron | Renderer process | ElectronBackend + 26 service classes that call invoke/on |

How It Works

  1. Main process calls setupElectronMain(win) — registers ~90 ipcMain.handle channels that map to native Electron APIs, Node.js builtins, and optional native modules.

  2. Preload script calls createEnclosurePreload() — exposes window.__enclosure with three methods: invoke (request/response), on (event subscription), once (one-shot event).

  3. Renderer process creates an ElectronBackend and calls registerElectronCapabilities(ctx) — all 26 service classes are bound to DI tokens. Each service calls window.__enclosure.invoke(channel, args) which crosses the process boundary via ipcRenderer.invoke.

  4. Application code never touches Electron directly — it calls context.use(FileSystemToken) and gets a FileSystem interface. Replace ElectronBackend with TauriBackend and every service swaps automatically.

Quick Start

1. Main process (main.ts)

import { app, BrowserWindow } from 'electron';
import { setupElectronMain } from '@enclosurejs/platform-electron/main';

app.whenReady().then(() => {
    const win = new BrowserWindow({
        webPreferences: {
            contextIsolation: true,
            preload: path.join(__dirname, 'preload.js'),
        },
    });

    setupElectronMain(win, { preloadPath: path.join(__dirname, 'preload.js') });
    win.loadFile('index.html');
});

2. Preload script (preload.ts)

import { createEnclosurePreload } from '@enclosurejs/platform-electron/preload';

createEnclosurePreload();

3. Renderer process (app.ts)

import { createApp, FileSystemToken } from '@enclosurejs/core';
import { ElectronBackend, registerElectronCapabilities } from '@enclosurejs/platform-electron';

const app = createApp({
    backend: new ElectronBackend(),
    frontend: {
        mount({ container, context }) {
            const fs = context.use(FileSystemToken);
            fs.readTextFile('/etc/hostname').then((text) => {
                container.textContent = text;
            });
        },
    },
    container: document.getElementById('app')!,
    modules: [
        {
            id: 'electron-caps',
            install(ctx) {
                registerElectronCapabilities(ctx.context);
            },
        },
    ],
});

await app.start();

API

Entrypoints

| Import path | Export | Purpose | | --------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | | @enclosurejs/platform-electron | ElectronBackend | BackendAdapter for renderer process | | | registerElectronCapabilities(ctx) | Registers all 26 capability tokens in one call | | | EnclosureElectronBridge | TypeScript interface for window.__enclosure | | | Electron* (26 classes) | Individual service implementations | | @enclosurejs/platform-electron/preload | createEnclosurePreload() | Sets up window.__enclosure bridge | | @enclosurejs/platform-electron/main | setupElectronMain(win?, opts?) | Registers ipcMain.handle handlers; returns boolean (false = quit) | | | updateMainWindow(win) | Update window reference after re-creation | | @enclosurejs/platform-electron/register | registerElectronCapabilities(ctx) | Same as main export (direct import) |

ElectronBackend

Implements BackendAdapter from @enclosurejs/core:

| Member | Type | Description | | ---------- | ----------------------------------------------------------- | --------------------------------------------- | | platform | 'electron' | Platform identifier | | invoke | (cmd: string, args?: Record<string, unknown>) => Promise | IPC request/response via ipcRenderer.invoke | | on | (event: string, handler: (payload) => void) => Disposable | IPC event subscription, idempotent dispose | | once | (event: string) => Promise | One-shot event, resolves on first message |

registerElectronCapabilities(ctx)

Binds all 26 service classes to their corresponding @enclosurejs/core tokens in a single call. Each token maps to exactly one Electron* class. Throws CoreError if called twice on the same context (token-already-provided).

setupElectronMain(win?, opts?)

Registers ~90 ipcMain.handle channels and acquires a single-instance lock. Returns false when a second instance is detected (caller should quit). On subsequent calls, updates the window reference without re-registering handlers. Accepts an optional preloadPath for child window creation.

IPC Protocol

All IPC channels follow the naming convention enclosure:{capability}:{method}:

enclosure:fs:readTextFile        → ipcMain.handle → node:fs/promises
enclosure:dialog:confirm         → ipcMain.handle → electron dialog
enclosure:shell:spawn            → ipcMain.handle → node:child_process
enclosure:serial:data:{connId}   → main → renderer (event stream)
enclosure:window:{id}:resize     → main → renderer (event stream)

Request flow: renderer → window.__enclosure.invoke(channel, args)ipcRenderer.invokeipcMain.handle → native API → response.

Event flow: main → webContents.send(channel, payload)ipcRenderer.onwindow.__enclosure.on(channel, handler) → service event handler.

Capability Implementations (26)

| Service class | Token | Main-process API | | ----------------------- | ------------------- | ----------------------------------------- | | ElectronDialogs | DialogToken | electron.dialog | | ElectronWindows | WindowToken | BrowserWindow | | ElectronIpc | IpcToken | ipcMain / webContents.send | | ElectronFileSystem | FileSystemToken | node:fs/promises | | ElectronShell | ShellToken | node:child_process | | ElectronClipboard | ClipboardToken | navigator.clipboard (renderer-side) | | ElectronNotifications | NotificationToken | new Notification() (renderer-side) | | ElectronStorage | StorageToken | localStorage (renderer-side, prefixed) | | ElectronHttp | HttpClientToken | net.fetch (main-process, bypasses CORS) | | ElectronUpdater | UpdaterToken | electron-updater (dynamic import) | | ElectronPath | PathToken | Pure JS path operations | | ElectronSystemInfo | SystemInfoToken | node:os | | ElectronTray | TrayToken | electron.Tray + Menu | | ElectronShortcuts | ShortcutToken | electron.globalShortcut | | ElectronDisplay | DisplayToken | electron.screen | | ElectronPrint | PrintToken | webContents.print | | ElectronSerial | SerialToken | serialport (optional dep) | | ElectronFileWatcher | FileWatcherToken | @parcel/watcher | | ElectronUsb | UsbToken | usb (optional dep) | | ElectronDatabase | DatabaseToken | better-sqlite3 (optional dep) | | ElectronDeepLink | DeepLinkToken | app.setAsDefaultProtocolClient | | ElectronAppLifecycle | AppLifecycleToken | electron.app + powerMonitor | | ElectronTheme | ThemeToken | electron.nativeTheme | | ElectronAutoLaunch | AutoLaunchToken | app.setLoginItemSettings | | ElectronPowerMonitor | PowerMonitorToken | electron.powerMonitor + Battery API | | ElectronKeychain | KeychainToken | electron.safeStorage + encrypted JSON |

Configuration

Zero configuration files. The package adapts to Electron's environment automatically:

  • Main process: setupElectronMain(win) — only requires a BrowserWindow reference. Optional preloadPath for child windows.
  • Renderer process: registerElectronCapabilities(ctx) — one call, no options.
  • Services: each service resolves its main-process counterpart by channel name. No env vars, no config objects.

Optional Dependencies

Four capabilities require native npm packages declared as optionalDependencies:

| Package | Capability | Install | Loaded via | | ------------------ | --------------- | --------------------------- | ------------------ | | better-sqlite3 | DatabaseService | pnpm add better-sqlite3 | createRequire() | | serialport | SerialService | pnpm add serialport | dynamic import() | | usb | UsbService | pnpm add usb | dynamic import() | | electron-updater | UpdaterService | pnpm add electron-updater | dynamic import() |

These are loaded at runtime via dynamic import() / createRequire(). If not installed, the service throws a descriptive error on first use — the rest of the application works fine.

Required dependency

| Package | Purpose | | ----------------- | ------------------------------------------------------------- | | @parcel/watcher | FileWatcherService — native file system watcher (no chokidar) |

Types Exported

Types other packages and application code depend on:

| Type | Used by | | ------------------------------ | ---------------------------------------------- | | EnclosureElectronBridge | Preload scripts, custom bridge implementations | | ElectronBackend | App entry point (renderer) | | All 26 Electron* classes | Direct use when bypassing DI (not recommended) | | registerElectronCapabilities | Module install() for DI registration | | setupElectronMain | Main process entry point | | updateMainWindow | macOS activate handler | | createEnclosurePreload | Preload script |

Entrypoint separation keeps process-specific types isolated:

| Import path | Runs in | Imports from Electron | | --------------------------------------- | ---------------- | --------------------- | | @enclosurejs/platform-electron | Renderer process | No (bridge only) | | @enclosurejs/platform-electron/preload | Preload script | Yes (contextBridge) | | @enclosurejs/platform-electron/main | Main process | Yes (full Electron) | | @enclosurejs/platform-electron/register | Renderer process | No (bridge only) |

Safety

Process Isolation

  • Renderer never imports electron — all access goes through window.__enclosure which is exposed via contextBridge with contextIsolation: true.
  • setupElectronMain() acquires a single-instance lock on first call and returns false if a second instance is detected. Subsequent calls update the window reference without re-registering handlers.
  • updateMainWindow() safely replaces the window reference for macOS activate re-creation.

Lifecycle Safety

  • ElectronBackend.on() returns Disposable with idempotent dispose() — double-dispose is safe.
  • All event-based services (IPC, shortcuts, tray, theme, power monitor, file watcher) properly unsubscribe on dispose.
  • registerElectronCapabilities() throws CoreError on double-call — prevents silent token shadowing.

Error Safety

  • All services throw CoreError with code BRIDGE_UNAVAILABLE when window.__enclosure is missing, pointing to preload misconfiguration.
  • Optional dependencies (better-sqlite3, serialport, usb) are loaded dynamically with try/catch — missing packages produce clear error messages, not cryptic crashes.
  • ElectronHttp wraps all net.fetch failures in CoreError with HTTP_REQUEST_FAILED code — including non-Error thrown values.
  • ElectronClipboard.writeImage wraps failures in CoreError with CLIPBOARD_WRITE_IMAGE code.
  • ElectronStorage.get() returns undefined for corrupt JSON — no throws on data corruption.

Data Safety

  • ElectronKeychain uses safeStorage.encryptString / decryptString — OS-level encryption (DPAPI on Windows, Keychain on macOS, libsecret on Linux).
  • ElectronDatabase enables WAL mode by default and runs migrations in a single transaction — partial migration is rolled back.
  • ElectronStorage prefixes all keys with __enclosure_store_ — no collision with other localStorage users.
  • ElectronShell.spawn() shallow-copies args and env — prevents mutation after call.

Deep Link Safety

  • app.requestSingleInstanceLock() enforces single instance — second instance sends URL via second-instance event and quits.
  • URL parsing uses new URL() with fallback for malformed URLs — no throws on invalid deep link input.

Platform Specifics

Renderer vs IPC Split

Not all services cross the IPC boundary. Three services run entirely in the renderer:

| Service | Renderer API | Why no IPC | | ----------------------- | --------------------- | -------------------------------------------- | | ElectronClipboard | navigator.clipboard | Web Clipboard API works in renderer directly | | ElectronNotifications | new Notification() | Web Notification API, Chromium handles it | | ElectronStorage | localStorage | Renderer-local, prefixed keys |

Two services are hybrid (renderer + IPC):

| Service | Renderer part | IPC part | | ---------------------- | -------------------------------------- | ----------------------------------------------------- | | ElectronAppLifecycle | DOM visibilitychange, focus/blur | quit, relaunch, hide, show, vetoQuit | | ElectronPowerMonitor | Battery API (navigator.getBattery()) | isOnBatteryPower, getSystemIdleTime, power events |

All other 21 services are pure IPC — every method call crosses ipcRenderer.invoke. ElectronHttp routes through IPC to net.fetch in the main process, bypassing renderer CORS restrictions.

Path — Pure JS, Not Node's path

ElectronPath is a custom JS implementation — it does NOT delegate to Node.js path module via IPC. Platform detection uses navigator.userAgent.includes('Windows') for sep and delimiter. The resolve() method uses a walk-from-the-end algorithm that may diverge from Node's path.resolve for edge cases (UNC paths, extended-length paths).

Storage — localStorage with Prefix

| Detail | Value | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | Backend | window.localStorage | | Key prefix | __enclosure_store_ | | Persistence | Per-origin, survives restart, NOT shared across windows with different origins | | size() | Approximate UTF-16 estimate (key.length*2 + value.length*2), not actual on-disk size | | get() on corrupt JSON | Returns undefined (silent JSON.parse catch) | | clear() | Removes all prefixed keys, then fires undefined to ALL registered onChange watchers (broadcast-style, not per-key) |

Database — better-sqlite3 via createRequire

| Detail | Value | | -------------- | -------------------------------------------------------------------------------- | | Location | {app.getPath('userData')}/databases/{name}.sqlite | | In-memory | :memory: path | | WAL mode | Enabled by default (PRAGMA journal_mode = WAL) | | Migrations | Sorted by version, run in one transaction, PRAGMA user_version tracks progress | | Loading | createRequire(import.meta.url)('better-sqlite3') — loaded once, cached |

Keychain — Encrypted JSON File

| Detail | Value | | ----------------- | ---------------------------------------------------------------------------- | | Location | {app.getPath('userData')}/enclosure-keychain.json | | Encryption | safeStorage.encryptString / decryptString (DPAPI / Keychain / libsecret) | | Format | { [service]: { [account]: password } } — vault structure, one file | | Corrupt vault | Returns {} (silent catch in readVault) |

Deep Link — Single Instance Lock

| Detail | Value | | ------------------- | --------------------------------------------------------- | | Single instance | app.requestSingleInstanceLock() — second instance quits | | Windows/Linux | second-instance event extracts URLs from argv | | macOS | open-url event handled separately | | URL parsing | new URL() with fallback for malformed URLs |

File Watcher — @parcel/watcher

| Detail | Value | | -------------------- | ----------------------------------------------------------------- | | Backend | @parcel/watcher (native, not chokidar/fs.watch) | | Rename detection | Correlates close delete+create pairs with matching base filenames | | Glob filtering | Regex approximation of glob patterns (not full glob semantics) | | Ignore patterns | Pushed to native watcher layer |

Shell — Raw Byte Streams

ElectronShell.spawn() sends stdout/stderr as Buffer → Uint8Array chunks over per-pid IPC channels (enclosure:shell:stdout:{pid}). This provides raw byte streams, unlike Tauri's line-buffered approach. kill() uses void invoke — rejections are not surfaced.

Shortcuts — Client-Side Registration Map

ElectronShortcuts.isRegistered() checks a renderer-side Map, not the main process or OS. If shortcuts are registered outside this service, isRegistered will return incorrect results.

Theme — Silent Degradation

If window.__enclosure is missing during construction, ElectronTheme initializes with 'light' / 'system' defaults and never subscribes to IPC updates. No error is thrown — the service silently operates with stale data.

AppLifecycle — Dual State Sources

State is driven by both DOM events (visibilitychange, focus, blur) and main-process IPC (enclosure:lifecycle:state for suspend/resume/lock). These can briefly disagree. blur sets state to 'inactive' which may not match OS "background" semantics.

IPC — Fire-and-Forget Sends

ElectronIpc.send() and broadcast() use void invokeerrors from the main process are not returned to the caller. getLastMessage() returns from a renderer-side cache only.

Notifications — Chromium Permissions

ElectronNotifications uses the Web Notification API in the renderer, not Electron's main-process Notification. Behavior depends on Chromium's notification permission model and OS notification settings.

Clipboard — PNG Only for Images

ElectronClipboard.readImage() reads only image/png from the clipboard. Other image formats are not supported. Any failure (permission denied, no image) returns null silently. writeImage() uses ClipboardItem — wraps failures in CoreError.

Deep Link — Silent No-Op Without Bridge

ElectronDeepLink.ensureListener() checks for window.__enclosure — if missing, it returns without subscribing. Handlers registered via onOpen() will never fire. No warning is logged.

Silent .catch(() => {}) Patterns

Several services silently swallow errors from event subscriptions or initial setup. If the bridge is missing or event registration fails, the service continues without the subscription:

| Service | Silent catch location | | ---------------------- | ---------------------------------------------------------- | | ElectronTheme | Initial get()/getSource() IPC calls in constructor | | ElectronDeepLink | ensureListener() — no subscription if no bridge | | ElectronPowerMonitor | getBattery() catch, battery change setup | | ElectronIpc | send() / broadcast()void invoke (fire-and-forget) | | ElectronShortcuts | register() / unregister()void invoke | | ElectronFileWatcher | unwatch() in dispose — void invoke |

Error Wrapping

All services throw CoreError consistently:

| Error code | Thrown by | Trigger | | ----------------------- | ----------------------------------- | ------------------------------------------- | | BRIDGE_UNAVAILABLE | All 26 services + ElectronBackend | window.__enclosure missing (preload skip) | | HTTP_REQUEST_FAILED | ElectronHttp | net.fetch failure or non-Error throw | | CLIPBOARD_WRITE_IMAGE | ElectronClipboard | ClipboardItem write failure | | FILE_WATCHER_TIMEOUT | ElectronFileWatcher | waitFor predicate not matched in time | | SHELL_STDIN_IGNORED | ElectronShell | write() called with stdin: 'ignore' |

Platform-Specific Main Process Behavior

The main-process setup (setupElectronMain) contains OS-specific branches:

| Service | OS branch | | ------------ | ------------------------------------------------------------------------------------------------------------- | | AutoLaunch | darwin: openAsHidden via app.setLoginItemSettings({ openAsHidden }); win32: --hidden flag in args | | AppLifecycle | darwin: app.hide() / app.show(); others: win.minimize() / win.show() | | Keychain | Encryption backend varies: DPAPI (Windows), Keychain (macOS), libsecret (Linux) |

Comparison with platform-tauri

Key implementation differences between the two platform packages:

| Aspect | Electron | Tauri | | -------------------- | --------------------------------------- | ------------------------------------------------ | | Architecture | 3 processes (main, preload, renderer) | 1 JS process (webview) + Rust core | | Storage backend | localStorage (renderer, prefix-based) | LazyStore (file-based, auto-flushed) | | Keychain | Encrypted JSON file (safeStorage) | OS native keyring (custom Rust plugin) | | Database path | {userData}/databases/{name}.sqlite | sqlite:{name}.db (plugin-managed) | | Shell streams | Raw byte Buffer chunks | Line-buffered text (line + '\n') | | Path impl | Pure JS, userAgent for OS detection | Pure JS, @tauri-apps/api/path for sep/delim | | Print | All methods via IPC to main | print() = window.print(); others via Rust | | Updater feed URL | Configurable at runtime | Fixed in tauri.conf.json (setFeedUrl throws) | | Deep link | requestSingleInstanceLock() + argv | Plugin-based (@tauri-apps/plugin-deep-link) | | HTTP | Main-process net.fetch() via IPC | @tauri-apps/plugin-http (Rust-backed fetch) | | Notifications | Web Notification API (renderer) | @tauri-apps/plugin-notification (native) |

Benchmarks

Not applicable. @enclosurejs/platform-electron is a platform bridge — its performance is dominated by IPC round-trip latency (ipcRenderer.invoke → main → response), which is ~0.1–0.5ms per call in a typical Electron app. The service layer adds negligible overhead (argument serialization, Uint8Array conversion). Real-world performance depends on:

  • Electron's IPC serialization overhead (structured clone)
  • Main-process handler execution time (native API calls)
  • Optional native module performance (better-sqlite3, @parcel/watcher, serialport, usb)

For production profiling, use Electron's built-in Performance API and Chrome DevTools.

Bundle Size

| Output | File | Size | | ------------ | ----------------- | --------- | | Runtime (JS) | index.js | 48.87 KB | | | register.js | 47.70 KB | | | main/setup.js | 64.63 KB | | | preload.js | 0.74 KB | | Types (DTS) | index.d.ts | 14.52 KB | | | register.d.ts | 0.24 KB | | | main/setup.d.ts | 0.70 KB | | | preload.d.ts | 0.26 KB | | Total JS | | 161.94 KB | | Total | | 177.66 KB |

index.js and register.js share most code (26 service classes). main/setup.js contains all ipcMain.handle registrations — the largest file because it includes native API wiring for all 26 capabilities plus single-instance lock, deep link handling, and optional dependency loading. preload.js is minimal — just contextBridge.exposeInMainWorld. All native dependencies (electron, @parcel/watcher, better-sqlite3, serialport, usb, electron-updater) are externalized in the build.

Quality

| Metric | Value | | --------------------- | ------------------------------------------------------------------------------------- | | Unit tests | 369 (all pass) | | Test files | 10 test + 1 mock helper = 11 | | Source files | 26 services + backend + preload + main setup + register + barrel + ambient types = 32 | | Dependencies | @enclosurejs/core, @parcel/watcher | | Optional dependencies | better-sqlite3, electron-updater, serialport, usb | | Dev dependencies | electron, tsup | | Coverage thresholds | statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90% |

Quality Layers

Layer 1: STATIC ANALYSIS (every commit)
  tsc --noEmit        strict mode, zero errors
  eslint              ESLint 9 flat config, zero warnings
  prettier --check    formatting

Layer 2: UNIT TESTS (every commit)
  369 tests           backend, register, 26 services, bridge mock
                      covers lifecycle, dispose, errors, edge cases
  v8 coverage         statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90%

Layer 3: BENCHMARKS
  N/A                 platform bridge — performance dominated by Electron IPC

Layer 4: PACKAGE HEALTH
  2 deps              @enclosurejs/core (workspace), @parcel/watcher (native)
  4 optional deps     native modules loaded dynamically, graceful failure
  tsup build          ESM + DTS output, 4 entrypoints

File Structure

packages/platform-electron/
├── src/
│   ├── index.ts                     ElectronBackend + EnclosureElectronBridge, re-exports
│   ├── optional-deps.d.ts          Ambient types for optional native modules
│   ├── preload.ts                   createEnclosurePreload (contextBridge setup)
│   ├── register.ts                  registerElectronCapabilities (26 tokens → DI)
│   ├── main/
│   │   └── setup.ts                 setupElectronMain — all ipcMain.handle registrations
│   ├── services/
│   │   ├── index.ts                 Barrel for all 26 Electron* service classes
│   │   ├── app-lifecycle.ts         ElectronAppLifecycle
│   │   ├── auto-launch.ts           ElectronAutoLaunch
│   │   ├── clipboard.ts             ElectronClipboard
│   │   ├── database.ts              ElectronDatabase
│   │   ├── deep-link.ts             ElectronDeepLink
│   │   ├── dialogs.ts               ElectronDialogs
│   │   ├── display.ts               ElectronDisplay
│   │   ├── file-watcher.ts          ElectronFileWatcher
│   │   ├── fs.ts                    ElectronFileSystem
│   │   ├── http.ts                  ElectronHttp
│   │   ├── ipc.ts                   ElectronIpc
│   │   ├── keychain.ts              ElectronKeychain
│   │   ├── notifications.ts         ElectronNotifications
│   │   ├── path.ts                  ElectronPath
│   │   ├── power-monitor.ts         ElectronPowerMonitor
│   │   ├── print.ts                 ElectronPrint
│   │   ├── serial.ts                ElectronSerial
│   │   ├── shell.ts                 ElectronShell
│   │   ├── shortcuts.ts             ElectronShortcuts
│   │   ├── storage.ts               ElectronStorage
│   │   ├── system-info.ts           ElectronSystemInfo
│   │   ├── theme.ts                 ElectronTheme
│   │   ├── tray.ts                  ElectronTray
│   │   ├── updater.ts               ElectronUpdater
│   │   ├── usb.ts                   ElectronUsb
│   │   └── windows.ts               ElectronWindows
│   └── __tests__/
│       ├── _bridge-mock.ts           Shared MockBridge for window.__enclosure
│       ├── backend.test.ts           7 tests — ElectronBackend adapter
│       ├── clipboard.test.ts         8 tests — ClipboardService (navigator.clipboard)
│       ├── http.test.ts              24 tests — HttpClient (IPC to net.fetch, errors)
│       ├── notifications.test.ts     8 tests — NotificationService (Web Notification API)
│       ├── path.test.ts              35 tests — PathService (Windows + Unix)
│       ├── register.test.ts          3 tests — registerElectronCapabilities (26 tokens)
│       ├── services-complex.test.ts  130 tests — FS, Database, IPC, Updater, Shortcuts,
│       │                                         Tray, Serial, USB, Shell, FileWatcher
│       ├── services-ipc.test.ts      56 tests — AutoLaunch, Keychain, Dialogs, Display,
│       │                                         SystemInfo, Print, DeepLink
│       ├── services-stateful.test.ts 71 tests — Theme, AppLifecycle, Windows, PowerMonitor
│       └── storage.test.ts           27 tests — StorageService (localStorage, onChange)
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.md

License

MIT