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-web

v1.1.0

Published

> [!IMPORTANT] > This package bridges `@enclosurejs/core` to the browser. It provides `WebPlatformBackend` (the `BackendAdapter`), and concrete implementations for 20 capability contracts using standard Web APIs. Your application code never imports this p

Readme

@enclosurejs/platform-web — Browser Backend + 20 Capability Implementations

[!IMPORTANT] This package bridges @enclosurejs/core to the browser. It provides WebPlatformBackend (the BackendAdapter), and concrete implementations for 20 capability contracts using standard Web APIs. Your application code never imports this package directly — it consumes capability tokens from @enclosurejs/core.

The Problem

Modern browsers expose rich APIs — Origin Private File System, Web Serial, WebUSB, BroadcastChannel, WebAssembly, Web Crypto — but each has its own surface, error model, and availability story. Wiring 20 capabilities to these APIs by hand means scattered feature detection, inconsistent error handling, and callsites tightly coupled to the browser platform. If you later want to switch to Tauri or Electron, every navigator.* and window.* call must be rewritten.

@enclosurejs/platform-web solves this by mapping every applicable @enclosurejs/core capability token to a browser implementation behind a single registration call. Application code talks to abstract interfaces via DI; switching to Tauri means swapping the import — zero changes to business logic.

Architecture

┌──────────────── Browser Tab (JS) ─────────────────────┐
│                                                        │
│  App code → context.use(FileSystemToken)               │
│               │                                        │
│  WebFileSystem.readTextFile(path)                      │
│               │                                        │
│  navigator.storage.getDirectory() → OPFS               │
│               │ (same-process, no IPC)                  │
└────────────────────────────────────────────────────────┘

Single layer, single process — no IPC bridge, no preload, no native process:

| Layer | Package | Runs in | Responsibility | | --------------- | ------------------------- | ----------- | ---------------------------------------------------- | | JS Services | @enclosurejs/platform-web | Browser tab | WebPlatformBackend + 20 service classes (Web APIs) |

How It Works

  1. No native side — unlike Tauri (Rust) or Electron (Node.js main process), the web platform runs entirely in the browser. All capabilities use standard Web APIs directly.

  2. WebPlatformBackend extends WebBackend from @enclosurejs/core — in-process invoke() and pushEvent() for commands and events. No serialization overhead.

  3. RegistrationregisterWebCapabilities(ctx) binds 18 service classes unconditionally, plus 2 conditionally (UsbToken, SerialToken) based on browser API availability.

  4. Application code calls context.use(ShortcutToken) and gets a ShortcutService interface. Replace WebPlatformBackend with TauriBackend and every service swaps automatically.

Capabilities Not Available on Web

Six capabilities from @enclosurejs/core have no browser equivalent and are intentionally omitted:

| Capability | Why unavailable | | --------------------- | -------------------------------------------------- | | ShellService | No subprocess access in browser sandbox | | TrayService | No system tray API | | AutoLaunchService | No start-on-login capability | | UpdaterService | No auto-update binary mechanism | | FileWatcherService | OPFS does not support fs.watch-style observation | | PowerMonitorService | Battery API deprecated and unreliable |

Check with context.has(ShellToken) before using — returns false on web.

Quick Start

import { createApp, FileSystemToken } from '@enclosurejs/core';
import { WebPlatformBackend, registerWebCapabilities } from '@enclosurejs/platform-web';

const app = createApp({
    backend: new WebPlatformBackend(),
    frontend: {
        mount({ container, context }) {
            const fs = context.use(FileSystemToken);
            fs.readTextFile('hello.txt').then((text) => {
                container.textContent = text;
            });
        },
    },
    container: document.getElementById('app')!,
    modules: [
        {
            id: 'web-caps',
            install(ctx) {
                registerWebCapabilities(ctx.context);
            },
        },
    ],
});

await app.start();

API

Entrypoints

| Import path | Export | Purpose | | ---------------------------------- | ------------------------------ | --------------------------------------------- | | @enclosurejs/platform-web | WebPlatformBackend | BackendAdapter for browser | | | registerWebCapabilities(ctx) | Registers 18–20 capability tokens in one call | | | Web* (20 classes) | Individual service implementations | | @enclosurejs/platform-web/register | registerWebCapabilities(ctx) | Same as main export (direct import) |

WebPlatformBackend

Extends WebBackend from @enclosurejs/core with platform = 'web':

| Member | Type | Description | | ----------------- | ----------------------------------------------------------- | ------------------------------------------ | | platform | 'web' | Platform identifier | | invoke | (cmd: string, args?: Record<string, unknown>) => Promise | In-process command dispatch (no IPC) | | on | (event: string, handler: (payload) => void) => Disposable | In-process pub/sub, idempotent dispose() | | once | (event: string) => Promise | One-shot event — resolves on first payload | | registerCommand | (cmd: string, handler: CommandHandler) => void | Bind a command handler for invoke | | pushEvent | (event: string, payload: unknown) => void | Emit an event to all on subscribers |

No async gap — on() is synchronous, dispose() is immediate. No leaked listeners possible.

registerWebCapabilities(ctx)

Binds 18 service classes unconditionally, plus 2 conditionally:

  • UsbToken — registered only if 'usb' in navigator (WebUSB API available)
  • SerialToken — registered only if 'serial' in navigator (Web Serial API available)

Throws CoreError if called twice on the same context (token-already-provided).

Capability Implementations (20)

| Service class | Token | Web API | | ------------------ | ------------------- | ------------------------------------------------------------- | | WebAppLifecycle | AppLifecycleToken | visibilitychange, focus/blur, beforeunload | | WebClipboard | ClipboardToken | navigator.clipboard (Clipboard API) | | WebDatabase | DatabaseToken | sql.js (SQLite via WASM, optional sql.js peer dep) | | WebDeepLink | DeepLinkToken | location.search/hash, registerProtocolHandler | | WebDialogs | DialogToken | File System Access API, <input> fallback, alert/confirm | | WebDisplay | DisplayToken | window.screen, devicePixelRatio | | WebFileSystem | FileSystemToken | Origin Private File System (OPFS) | | WebHttp | HttpClientToken | fetch (with AbortController, progress streams) | | WebIpc | IpcToken | BroadcastChannel (inter-tab messaging) | | WebKeychain | KeychainToken | IndexedDB + Web Crypto API (AES-256-GCM, PBKDF2) | | WebNotifications | NotificationToken | Web Notifications API | | WebPath | PathToken | Pure JS POSIX path operations | | WebPrint | PrintToken | window.print(), iframe-based printUrl/printHtml | | WebSerial | SerialToken | Web Serial API (navigator.serial) | | WebShortcuts | ShortcutToken | document.addEventListener('keydown') + accelerator parsing | | WebStorage | StorageToken | localStorage (prefixed keys, JSON serialization) | | WebSystemInfo | SystemInfoToken | navigator.userAgent, hardwareConcurrency, deviceMemory | | WebTheme | ThemeToken | matchMedia('prefers-color-scheme') + localStorage | | WebUsb | UsbToken | WebUSB API (navigator.usb) | | WebWindows | WindowToken | window.open(), popup management |

Configuration

Near-zero configuration. The package uses browser-native APIs directly:

  • Registration: registerWebCapabilities(ctx) — one call, no options.
  • Conditional tokens: UsbToken and SerialToken are auto-detected via feature check.
  • Database WASM: WebDatabase accepts optional WebDatabaseConfig with locateFile to override the default jsDelivr CDN URL for the sql.js WASM file.
  • Other services: wrap browser globals directly. No env vars, no config objects.

Browser compatibility considerations

| API | Used by | Availability | | -------------------------- | ---------- | -------------------------------------------- | | navigator.clipboard | Clipboard | All modern browsers (HTTPS required) | | Origin Private File System | FileSystem | Chrome 86+, Firefox 111+, Safari 15.2+ | | BroadcastChannel | IPC | All modern browsers | | Web Crypto API | Keychain | All modern browsers (HTTPS required) | | WebAssembly | Database | All modern browsers (required for sql.js) | | Web Serial API | Serial | Chrome 89+ (experimental, Chromium only) | | WebUSB API | USB | Chrome 61+ (experimental, Chromium only) | | File System Access API | Dialogs | Chrome 86+ (fallback to <input> elsewhere) | | registerProtocolHandler | DeepLink | Firefox, Chrome (not Safari) | | window.open | Windows | All browsers (popup blockers may interfere) |

Types Exported

Types other packages and application code depend on:

| Type | Used by | | ------------------------- | ----------------------------------------------------- | | WebPlatformBackend | App entry point (browser) | | All 20 Web* classes | Direct use when bypassing DI (not recommended) | | WebDatabaseConfig | Optional config for WebDatabase (WASM locateFile) | | registerWebCapabilities | Module install() for DI registration |

Entrypoint separation:

| Import path | Contains | | ---------------------------------- | ------------------------------------ | | @enclosurejs/platform-web | Backend + all 20 services + register | | @enclosurejs/platform-web/register | registerWebCapabilities only |

Both entrypoints use standard Web APIs — this package is usable in any modern browser context.

Safety

Lifecycle Safety

  • WebPlatformBackend inherits WebBackendon() returns Disposable with idempotent dispose(). Double-dispose is safe.
  • registerWebCapabilities() throws CoreError on double-call — prevents silent token shadowing.
  • All event-based services (IPC, shortcuts, theme, windows) properly unsubscribe on dispose.
  • WebSerial connection onData read loop stops cleanly on close() — no leaked readers.

Error Safety

  • WebHttp wraps all fetch failures in CoreError with HTTP_REQUEST_FAILED code.
  • WebClipboard.writeImage() wraps failures in CoreError with CLIPBOARD_WRITE_IMAGE code.
  • WebDatabase throws CoreError with DB_INIT_FAILED when sql.js WASM fails to load, and DB_TRANSACTION_FAILED on transaction rollback.
  • WebDialogs throws CoreError with DIALOG_UNSUPPORTED when experimental APIs are unavailable.
  • WebSerial throws CoreError with SERIAL_NOT_WRITABLE when port has no writable stream.
  • WebUsb throws CoreError with USB_NO_INTERFACE when no interface owns the target endpoint.
  • WebFileSystem throws CoreError with FS_INVALID_PATH for empty or root-only paths.
  • WebStorage.get() returns undefined for corrupt JSON — no throws on data corruption.

Data Safety

  • WebKeychain encrypts secrets with AES-256-GCM (PBKDF2 key derivation, location.origin as salt) — stored in IndexedDB, scoped to origin. Database connection is closed after every operation.
  • WebStorage prefixes all keys with __enclosure_store_ — no collision with other localStorage users.
  • WebDatabase uses sql.js in-memory SQLite — each open() creates an isolated database with no IndexedDB collision risk.
  • WebTheme persists user preference to localStorage under __enclosure_theme — survives page reload.

Sandbox Safety

  • All services operate within the browser sandbox — no filesystem access outside OPFS, no process spawning, no system-level modifications.
  • WebSerial and WebUsb require user gesture (browser permission prompt) — cannot enumerate or connect silently.

Platform Specifics

Single-Process, No IPC

Unlike Tauri (webview + Rust) or Electron (renderer + main + preload), the web platform runs in a single JS context. WebPlatformBackend extends WebBackendinvoke() dispatches to in-process command handlers, pushEvent() delivers to in-process subscribers. Zero serialization overhead.

File System — Origin Private File System (OPFS)

| Detail | Value | | ----------------- | --------------------------------------------------------------------- | | Backend | navigator.storage.getDirectory()FileSystemDirectoryHandle | | Scope | Origin-private — invisible to OS file manager, sandboxed per origin | | watch() | No-op — returns a Disposable that does nothing (OPFS has no events) | | rename() | Implemented as read → write → delete (no native rename in OPFS) | | Invalid paths | '' and '/' throw CoreError with FS_INVALID_PATH |

Storage — localStorage with Prefix

| Detail | Value | | --------------------------- | ------------------------------------------------------------------- | | Backend | window.localStorage | | Key prefix | __enclosure_store_ | | Persistence | Per-origin, survives restart | | size() | Approximate UTF-16 estimate (key.length*2 + value.length*2) | | get() on corrupt JSON | Returns undefined (silent JSON.parse catch) | | clear() | Removes prefixed keys, fires undefined to all onChange watchers |

Database — sql.js (SQLite via WASM)

| Detail | Value | | ---------------- | ----------------------------------------------------------------------------- | | Backend | sql.js (SQLite compiled to WASM), loaded on first open() call | | WASM source | jsDelivr CDN by default, override with WebDatabaseConfig.locateFile | | Peer dep | sql.js (optional) — dynamic import, tree-shakes when unused | | Migrations | DbOpenOptions.migrations applied via PRAGMA user_version versioning | | Transactions | Real SQL transactions (BEGIN / COMMIT / ROLLBACK) | | Errors | DB_INIT_FAILED when WASM fails to load, DB_TRANSACTION_FAILED on rollback |

Keychain — IndexedDB + Web Crypto

| Detail | Value | | -------------- | ------------------------------------------------------------ | | Storage | IndexedDB (__enclosure_keychain database, secrets store) | | Encryption | AES-256-GCM with PBKDF2-derived key (100,000 iterations) | | Salt | location.origin — keys are origin-scoped | | Key format | {service}::{account} — composite key |

IPC — BroadcastChannel

| Detail | Value | | ---------------------- | -------------------------------------------------------------------------------- | | Backend | BroadcastChannel — same-origin inter-tab messaging | | send() target | Targeted delivery — filtered by windowId on the receiving side | | broadcast() | Delivers to local handlers AND remote tabs | | getLastMessage() | Per-channel cache, updated only when delivered through an active on() listener | | Sender ID | windowId from WebWindows (URL __windowId param or 'main') |

Path — Pure JS POSIX

WebPath implements POSIX-style path operations with / separator and : delimiter. All methods are pure JS — no platform detection, no native calls. This is correct for the web platform where all paths (OPFS, URLs) use forward slashes.

Windows — window.open() Popups

| Detail | Value | | ------------------------ | ------------------------------------------------------------------ | | open() | window.open() with features string (width, height, left, top) | | openModal() | Polls win.closed every 200ms — resolves undefined if no result | | getCurrent() | Promise<AppWindow> — proxy to the current window object | | Stubs (no-op) | minimize, maximize, hide, setAlwaysOnTop, setKiosk, etc. | | Closed window access | Throws CoreError with WINDOW_CLOSED |

Shortcuts — Accelerator Parsing

WebShortcuts parses Electron/Tauri-style accelerator strings (CmdOrCtrl+Shift+P) into modifier+key combinations and listens on document.addEventListener('keydown', ..., true). Supports Ctrl, Shift, Alt/Option, Meta/Cmd/Command/Super, and CmdOrCtrl/CommandOrControl (auto-detects Mac via navigator.userAgent).

Theme — matchMedia + localStorage

| Detail | Value | | -------------------- | ------------------------------------------------------------------- | | System detection | matchMedia('(prefers-color-scheme: dark)') with change listener | | User override | Stored in localStorage under __enclosure_theme | | set('system') | Clears stored override, reverts to system detection | | Source tracking | getSource() returns 'system' or 'user' based on last action |

SystemInfo — Best-Effort Browser Detection

| Method | Source | | ------------ | -------------------------------------------------------------------------------------- | | arch() | Regex on navigator.userAgent (x86_64, aarch64, arm, x86) | | platform() | Regex on navigator.userAgent (android → ios → windows → macos → linux) | | hostname() | location.hostname (fallback: 'localhost') | | cpus() | Single entry: { model: 'Browser (estimated)', speed: 0, cores: hardwareConcurrency } | | memory() | navigator.deviceMemory × 1GB (fallback: 4GB), free: total - usedJSHeapSize | | uptime() | Math.floor(performance.now() / 1000) (tab uptime, not OS) | | homedir() | Always '/' | | tmpdir() | Always '/tmp' |

Print — Mixed Implementation

| Method | Implementation | | ------------- | ------------------------------------------------------------ | | print() | window.print() — browser print dialog, ignores options | | printUrl() | Hidden iframe, loads URL, calls contentWindow.print() | | printHtml() | Hidden iframe, sets srcdoc to HTML, calls print() |

Comparison with platform-tauri / platform-electron

| Aspect | Web | Tauri | Electron | | ------------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------------- | | Architecture | Single JS context (no IPC) | 1 JS process (webview) + Rust core | 3 processes (main, preload, renderer) | | Capabilities | 20 of 26 (6 unavailable) | 26 of 26 | 26 of 26 | | Storage backend | localStorage (prefix-based) | LazyStore (file-based, auto-flushed) | localStorage (prefix-based) | | Keychain | IndexedDB + Web Crypto (origin-scoped) | OS native keyring (custom Rust plugin) | Encrypted JSON file (safeStorage) | | Database | sql.js (SQLite via WASM, in-memory) | @tauri-apps/plugin-sql (SQLite via Rust) | better-sqlite3 (native SQLite) | | File System | OPFS (sandboxed, no watch) | Native FS via plugin | node:fs/promises | | Path style | POSIX only (/) | OS-native (auto-detect sep) | OS-native (userAgent-based) | | HTTP | Browser fetch() | @tauri-apps/plugin-http (Rust-backed) | Renderer fetch() | | IPC | BroadcastChannel (inter-tab) | @tauri-apps/api/event (inter-webview) | ipcMain/ipcRenderer | | Serial/USB | Web Serial / WebUSB (Chromium only) | Custom Rust plugins | serialport / usb (native npm) | | Windows | window.open() popups | WebviewWindow (native) | BrowserWindow (native) | | Shortcuts | keydown listener + accelerator parsing | @tauri-apps/plugin-global-shortcut | electron.globalShortcut |

Benchmarks

Not applicable. @enclosurejs/platform-web is a platform bridge — its performance is dominated by the underlying browser APIs (OPFS access time, sql.js query speed, fetch overhead). The service layer adds negligible overhead (argument forwarding, error wrapping). Real-world performance depends on:

  • Browser engine implementation of Web APIs (V8, SpiderMonkey, WebKit)
  • OPFS access latency (~0.1–1ms per operation depending on browser)
  • sql.js WASM initialization time (one-time, cached after first open())
  • Network latency for fetch-based operations

For production profiling, use browser DevTools Performance tab.

Bundle Size

| Output | File | Size | | ------------ | --------------- | --------- | | Runtime (JS) | index.js | 57.84 KB | | | register.js | 57.49 KB | | Types (DTS) | index.d.ts | 14.17 KB | | | register.d.ts | 0.76 KB | | Total JS | | 115.33 KB | | Total | | 130.26 KB |

sql.js is a dynamic import (peer dependency, optional) — not bundled into index.js/register.js. index.js and register.js share most code (20 service classes). DTS output includes all 20 service classes and the WebPlatformBackend type.

Quality

| Metric | Value | | ------------------- | ------------------------------------------------------------------ | | Unit tests | 446 (all pass) | | Test files | 22 (one per service + backend + register) | | Source files | 20 services + backend + register + barrels = 23 | | Dependencies | @enclosurejs/core | | Peer dependencies | sql.js (optional — for WebDatabase) | | Dev dependencies | sql.js, 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)
  446 tests           backend, register, 20 services (one file each)
                      covers lifecycle, dispose, errors, edge cases
                      all browser APIs mocked via vi.fn / Object.defineProperty
  v8 coverage         statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90%

Layer 3: BENCHMARKS
  N/A                 platform bridge — performance dominated by browser APIs

Layer 4: PACKAGE HEALTH
  1 peer dep          sql.js (optional, for WebDatabase)
  tsup build          ESM + DTS output, 2 entrypoints

File Structure

packages/platform-web/
├── src/
│   ├── index.ts                    WebPlatformBackend, re-exports services + register
│   ├── register.ts                 registerWebCapabilities (18+2 tokens → DI)
│   ├── sql-js.d.ts                 Module declaration for sql.js (no bundled types)
│   ├── services/
│   │   ├── index.ts                Barrel for all 20 Web* service classes
│   │   ├── app-lifecycle.ts        WebAppLifecycle
│   │   ├── clipboard.ts            WebClipboard
│   │   ├── database.ts             WebDatabase
│   │   ├── deep-link.ts            WebDeepLink
│   │   ├── dialogs.ts              WebDialogs
│   │   ├── display.ts              WebDisplay
│   │   ├── fs.ts                   WebFileSystem
│   │   ├── http.ts                 WebHttp
│   │   ├── ipc.ts                  WebIpc
│   │   ├── keychain.ts             WebKeychain
│   │   ├── notifications.ts        WebNotifications
│   │   ├── path.ts                 WebPath
│   │   ├── print.ts                WebPrint
│   │   ├── serial.ts               WebSerial
│   │   ├── shortcuts.ts            WebShortcuts
│   │   ├── storage.ts              WebStorage
│   │   ├── system-info.ts          WebSystemInfo
│   │   ├── theme.ts                WebTheme
│   │   ├── usb.ts                  WebUsb
│   │   └── windows.ts              WebWindows
│   └── __tests__/
│       ├── app-lifecycle.test.ts    22 tests — AppLifecycle (state, quit, relaunch)
│       ├── backend.test.ts          8 tests — WebPlatformBackend adapter
│       ├── clipboard.test.ts       11 tests — ClipboardService (navigator.clipboard)
│       ├── database.test.ts        18 tests — DatabaseService (sql.js, migrations, tx)
│       ├── deep-link.test.ts       18 tests — DeepLink (register, hash/search parsing)
│       ├── dialogs.test.ts         20 tests — DialogService (FSA API, fallback, alert)
│       ├── display.test.ts          9 tests — DisplayService (screen, devicePixelRatio)
│       ├── fs.test.ts              30 tests — FileSystem (OPFS, read, write, stat, copy)
│       ├── http.test.ts            29 tests — HttpClient (fetch, progress, errors)
│       ├── ipc.test.ts             28 tests — IpcService (BroadcastChannel, send, on)
│       ├── keychain.test.ts        18 tests — Keychain (IndexedDB + Web Crypto)
│       ├── notifications.test.ts   10 tests — Notifications (Web Notification API)
│       ├── path.test.ts            39 tests — PathService (POSIX join, parse, resolve)
│       ├── print.test.ts            6 tests — PrintService (window.print, iframe)
│       ├── register.test.ts         7 tests — registerWebCapabilities (18+2 tokens)
│       ├── serial.test.ts          12 tests — SerialService (Web Serial API)
│       ├── shortcuts.test.ts       24 tests — ShortcutService (accelerator parsing)
│       ├── storage.test.ts         23 tests — StorageService (localStorage, onChange)
│       ├── system-info.test.ts     24 tests — SystemInfo (userAgent parsing, memory)
│       ├── theme.test.ts           16 tests — ThemeService (matchMedia, localStorage)
│       ├── usb.test.ts             19 tests — UsbService (WebUSB, transfers, events)
│       └── windows.test.ts         55 tests — WindowService (open, modal, popups)
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.md

License

MIT