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/plugin-loader

v1.1.0

Published

Runtime plugin discovery, validation, and loading for Enclosure apps

Readme

@enclosurejs/plugin-loader — Runtime plugin discovery and validation

[!IMPORTANT] This is a universal module that provides stateless utilities for loading runtime-delivered plugins into Enclosure apps. It validates manifest metadata, checks @enclosurejs/core version compatibility, verifies capability availability, enforces application targeting, and creates isolated DI contexts for capability gating — all before a plugin touches the running application.

The Problem

Compile-time plugins are bundled into the application and share full DI access. Runtime-delivered plugins (mnemoscheme panels, device screens, third-party extensions) arrive as separate bundles with a manifest.json descriptor. Without a loader layer, the host application would need to manually:

  • Parse and validate manifest schemas with meaningful error messages
  • Check semver compatibility between the plugin and the running core
  • Verify that all required platform capabilities are actually provided
  • Enforce that a plugin only runs in the application it targets
  • Restrict DI access to declared capabilities only (capability gating)

@enclosurejs/plugin-loader solves this with pure functions that compose into a single validate()loadPluginEntry()createScopedContext() pipeline. No side effects, no global state, no platform dependencies.

Architecture

manifest.json ──▶ parseManifest() ──▶ PluginManifest
                                           │
                                    validate(manifest, ctx, ver, appId)
                                           │
                                    ┌──────┼──────────────────┐
                                    │      │                  │
                              checkCompat  checkCaps    checkAppTarget
                                    │      │                  │
                                    └──────┼──────────────────┘
                                           │
                              loadPluginEntry(url) ──▶ Plugin
                                           │
                         createScopedContext(ctx, caps) ──▶ scoped Context
                                           │
                                    app.installPlugin(plugin)

The loader exports stateless functions — it does not read files or manage plugin lifecycle. The caller (or a future scan()/watch() integration) owns I/O via FileSystem and FileWatcherService capabilities.

Quick Start

import {
    parseManifest,
    validate,
    loadPluginEntry,
    createScopedContext,
} from '@enclosurejs/plugin-loader';
import { isErr } from '@enclosurejs/core';

// 1. Parse manifest from raw JSON text (caller owns file I/O)
const manifestResult = parseManifest(jsonText);
if (isErr(manifestResult)) throw manifestResult.error;
const manifest = manifestResult.value;

// 2. Pre-install validation: version + capabilities + app target
const check = validate(manifest, app.context, undefined, app.appId);
if (isErr(check)) {
    console.warn(`Skipping plugin ${manifest.id}: ${check.error.message}`);
    return;
}

// 3. Dynamic import of the plugin entry
const pluginResult = await loadPluginEntry(new URL(manifest.entry, baseUrl).href);
if (isErr(pluginResult)) throw pluginResult.error;

// 4. Create a scoped context (only declared capabilities visible)
const scopedCtx = createScopedContext(app.context, manifest.capabilities);

// 5. Install into the running application
await app.installPlugin(pluginResult.value);

API

| Export | Kind | Purpose | | --------------------- | --------- | ---------------------------------------------------------------- | | parseManifest | function | Parse JSON text → Result<PluginManifest> | | validateManifest | function | Validate parsed JSON object → Result<PluginManifest> | | validate | function | Combined version + capabilities + app-target check | | checkCompatibility | function | Semver range check: manifest.enclosure vs running core version | | checkCapabilities | function | Verify all declared capability tokens exist in DI context | | checkAppTarget | function | Verify plugin targets the current application (or is universal) | | satisfies | function | Semver range matcher (>=, ^, ~, exact, *) | | loadPluginEntry | function | Dynamic import + validate named plugin export | | createScopedContext | function | Isolated DI context with only well-known + declared capabilities | | PluginManifest | interface | Typed manifest schema |

PluginManifest

| Field | Type | Required | Description | | -------------- | ----------------------- | -------- | ---------------------------------------------------------- | | id | string | yes | Unique plugin identifier — must match Plugin.id in entry | | name | string \| undefined | no | Human-readable display name | | version | string | yes | Semver of the plugin, for update detection | | entry | string | yes | Relative path to bundled JS entry (e.g. "./index.js") | | enclosure | string | yes | Semver range of compatible @enclosurejs/core versions | | capabilities | string[] \| undefined | no | Capability short names the plugin requires | | app | string[] \| undefined | no | Application IDs this plugin targets (omit for universal) |

Error Codes

All errors are CoreError instances with domain 'manifest' or 'plugin-loader':

| Code | Domain | When | | ---------------------- | ------------- | -------------------------------------------------------- | | INVALID_FORMAT | manifest | Raw value is not a JSON object | | MISSING_FIELD | manifest | Required field missing or wrong type | | INVALID_FIELD | manifest | Optional field has wrong type | | INCOMPATIBLE_VERSION | manifest | enclosure range not satisfied by running core version | | UNKNOWN_CAPABILITY | manifest | Capability short name not in capabilityTokenMap | | MISSING_CAPABILITY | manifest | Capability token not provided in DI context | | APP_NOT_SET | manifest | Plugin targets specific apps but no appId configured | | APP_MISMATCH | manifest | Plugin does not target current appId | | INVALID_JSON | plugin-loader | parseManifest received unparseable text | | IMPORT_FAILED | plugin-loader | Dynamic import threw (network error, syntax error, etc.) | | MISSING_EXPORT | plugin-loader | Entry module has no named plugin export | | INVALID_PLUGIN | plugin-loader | plugin export missing id string |

Configuration

Zero configuration. All functions are pure and stateless — behavior is controlled entirely through arguments.

Types Exported

| Type | Used by | | ---------------- | ------------------------------------------------------- | | PluginManifest | Any code that reads, validates, or displays plugin info |

Safety

Error Model

All validation failures return Result<void> with structured CoreError — never throw. Import failures and schema violations are always surfaced as err(CoreError(...)). The caller decides how to handle: skip the plugin, log a warning, or propagate.

Capability Gating

createScopedContext builds a standalone Context (no parent chain) with only well-known tokens (BackendToken, LifecycleToken, EventBusToken, AppToken, WidgetRegistryToken) and tokens matching declared capabilities. The plugin cannot resolve undeclared capability tokens via parent traversal.

If capabilities is undefined or empty, a plain createChild() is returned (no restriction — same as compile-time plugins).

Semver Engine

Minimal implementation covering common range patterns (>=, ^, ~, exact, *). Pre-release labels and complex boolean ranges (||, spaces) are not supported — returns false for unrecognized patterns.

Benchmarks

Not applicable. All functions are thin in-memory validation (JSON parsing, string comparisons, Map lookups). Performance is dominated by dynamic import() of the plugin entry, which is I/O-bound and outside this package's control.

Bundle Size

| Output | File | Size | | ------------ | ------------ | -------- | | Runtime (JS) | index.js | 8.49 KB | | Types (DTS) | index.d.ts | 2.55 KB | | Total | | 11.04 KB |

Single entrypoint. Single external dependency (@enclosurejs/core) marked as external in the build.

Quality

| Metric | Value | | --------------------- | ------------------------------------------------------------------ | | Unit tests | 64 (all pass) | | Test files | 3 (manifest, loader, scoped-context) | | Source files | 4 (manifest, scoped-context, loader, index) | | Dependencies | 1 (@enclosurejs/core — workspace peer) | | External dependencies | 0 (devDependencies only: 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)
  64 tests            manifest (44), loader (11), scoped-context (9)
                      covers schema validation (all fields, edge cases),
                      semver ranges (>=, ^, ~, exact, *), compatibility,
                      capability lookup (unknown/missing), app targeting,
                      dynamic import (mock), scoped context isolation,
                      combined validate(), parseManifest JSON errors
  v8 coverage         statements >= 90%, branches >= 85%, functions >= 95%, lines >= 90%

Layer 3: BENCHMARKS
  N/A                 in-memory validation — no meaningful benchmark target

Layer 4: PACKAGE HEALTH
  1 workspace dep     @enclosurejs/core (types + CoreError + DI, externalized in build)
  tsup build          ESM + DTS output, single entrypoint

File Structure

packages/plugin-loader/
├── src/
│   ├── index.ts              Barrel: manifest types, validation, loader, scoped context
│   ├── manifest.ts           PluginManifest, validateManifest, semver, compatibility checks
│   ├── scoped-context.ts     createScopedContext — isolated DI for capability gating
│   ├── loader.ts             validate, parseManifest, loadPluginEntry
│   └── __tests__/
│       ├── manifest.test.ts       44 tests — schema, semver, compat, capabilities, app target
│       ├── loader.test.ts         11 tests — validate, parseManifest, loadPluginEntry
│       └── scoped-context.test.ts  9 tests — well-known tokens, capability filtering, empty caps
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.md

License

MIT