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

@enms-vis/enms-vis-create

v2.0.2

Published

CLI tool to bootstrap enms-vis visualization projects

Readme

@enms-vis/enms-vis-create

CLI scaffolding tool for enms-vis SVG visualization projects.


Table of Contents


Getting Started

1. Create a new project

npx @enms-vis/enms-vis-create my-visualization

The CLI prompts for a project name (if not provided) and scaffolds a ready-to-run Vue + Vite project with an example SVG visualization.

2. Install dependencies

cd my-visualization
npm install

3. Start the development server

npm run dev

Vite starts a local dev server with hot-reload. Open the printed URL in your browser to see the visualization. The plugin runs in standalone mode when opened outside of an enms-web iframe – a local mock connection is created automatically so you can develop without a running enms-web instance.

4. Edit the visualization

The scaffolded project contains:

| Path | Purpose | |---|---| | src/components/SvgVisualization.vue | Main Vue component – links your SVG as a template | | src/assets/visualization.svg | SVG artwork – use Vue template syntax directly inside it | | src/assets/visualization.css | Styles scoped to the visualization | | enms-plugin-manifest.json | Declares inputs the visualization expects from enms-web |

Open the SVG in Inkscape (or any editor), add shapes, then use Vue bindings ({{ }}, v-on:click, :fill, etc.) via the XML editor to connect them to live data.

5. Build for deployment

npm run build

The output in dist/ together with enms-plugin-manifest.json forms the deployable plugin package (see Building for enms-web).


Tools & Packages

| Package | Purpose | |---|---| | @enms-vis/enms-vis-create | CLI tool that scaffolds new visualization projects. Generates a preconfigured Vue + Vite project with an example SVG, manifest, and all necessary wiring. | | @enms-vis/enms-web-plugin | Framework-agnostic communication library. Handles the iframe ↔ host messaging protocol (input delivery, patching, click events) between a visualization and enms-web. | | @enms-vis/enms-web-plugin-vue | Vue integration layer. Provides the useWebPlugin() composable that wraps @enms-vis/enms-web-plugin into reactive Vue state, lifecycle management, and convenient helpers. |


API Reference & Examples

useWebPlugin

The main entry point. Call it inside a Vue component's setup() to connect to the enms-web host and get reactive access to input data.

function useWebPlugin<TState = unknown>(pluginName?: string, options?: UseWebPluginOptions): UseWebPluginReturn<TState>

Best practice: Always pass a pluginName and declare the TState type parameter when your plugin persists state. The pluginName is used as the localStorage key prefix in standalone mode and appears in log messages; TState gives you full type-safety for pluginState and stateStorage.saveState().

<script lang="ts">
import { defineComponent } from 'vue'
import { useWebPlugin } from '@enms-vis/enms-web-plugin-vue'

type PluginState = { zoom: number }

export default defineComponent({
  name: 'SvgVisualization',
  setup() {
    // Recommended: explicit TState + pluginName
    const webPlugin = useWebPlugin<PluginState>('SvgVisualization');
    return {
      ...webPlugin
    }
  },
})
</script>

Options (UseWebPluginOptions)

options is an optional object that controls both the underlying host connection and the built-in editor button overlay.

| Option | Type | Default | Description | |---|---|---|---| | editButton | boolean \| HTMLElement \| undefined | undefined | Controls when the Input Payload Editor button overlay is mounted. See table below. | | allowedOrigins | string[] | ['*'] | Allowed origins for postMessage communication. Restrict this in production for extra security (e.g. ['https://your-enms-host.example.com']). | | timeout | number | 2000 | Host connection timeout in milliseconds. If the handshake with the parent window does not complete within this time, the plugin falls back to standalone mode. |

editButton behaviour:

| Value | Connected (iframe) mode | Standalone / detached mode | |---|---|---| | undefined (default) | Auto – shown only while edit mode is active | Always shown | | false | Never shown | Never shown | | true | Always shown (ignores edit-mode state) | Always shown | | HTMLElement | Like true, but mounted inside the given element | Like true, but mounted inside the given element |

<script lang="ts">
import { defineComponent } from 'vue'
import { useWebPlugin } from '@enms-vis/enms-web-plugin-vue'

export default defineComponent({
  name: 'SvgVisualization',
  setup() {
    return {
      ...useWebPlugin(
        'SvgVisualization',
        {
          editButton: false,           // never show the editor button
          allowedOrigins: ['https://enms.example.com'],
          timeout: 3000,
        },
      )
    }
  },
})
</script>

useWebPlugin() returns:

| Property | Type | Description | |---|---|---| | V | PluginStateValues | Reactive map of input data keyed by inputId | | debugMode | Ref<boolean> | Whether the host enabled debug mode | | editMode | Ref<boolean> | Whether the dashboard is currently in edit mode | | isStandalone | Ref<boolean> | Whether the plugin is running without a host connection (e.g. in dev mode) | | widgetName | Ref<string \| null> | Human-readable name of the widget instance hosting the plugin | | enmsVersion | Ref<string \| null> | Version string of the ENMS application | | pluginState | Ref<TState \| null \| undefined> | Last plugin state received from the host; null = nothing saved yet; undefined = not yet received | | stateStorage | ShallowRef<StateStorage<TState> \| null> | Abstraction for saving and loading plugin state and editor saved-states | | emitClick | Function | Emit a click event to the host | | pluginLog | Function | Plugin-scoped logging (debug logs only when debugMode is on) | | deviceParam | Function | Get a single parameter from a device input | | mockClient | WebPluginClient \| null | Available only in standalone/dev mode for pushing mock data |


Reactive State (V)

The V object is a reactive map whose keys match the inputId values declared in enms-plugin-manifest.json. Use it directly in SVG templates:

<!-- Display device name -->
<text x="60" y="114" font-family="sans-serif" font-size="20">
  {{ V?.device1?.name ?? '–' }}
</text>

<!-- Display a parameter value with unit -->
<text x="60" y="220" font-family="monospace" font-size="22">
  {{ V?.device1?.params?.active_power?.currentValue?.valueEnd?.toFixed(2) ?? '–' }}
  {{ V?.device1?.params?.active_power?.currentValue?.valueUnit ?? '' }}
</text>

deviceParam Helper

deviceParam(deviceInputId, paramCodeOrInputId) is a shorthand for accessing a single parameter from a device input. It returns a ParamInputPayload or null.

const param = this.deviceParam('ekostat_1', 'temperature_actual');
// param?.currentValue?.valueEnd   → numeric value
// param?.currentValue?.valueUnit  → unit string (e.g. '°C')
// param?.code                     → parameter code
// param?.name                     → parameter name

Computed Properties Pattern

For cleaner templates, define Vue computed properties that use deviceParam to extract and format values. This keeps the SVG template readable and the formatting logic in one place.

<script lang="ts">
import { defineComponent } from 'vue'
import { useWebPlugin } from '@enms-vis/enms-web-plugin-vue'

export default defineComponent({
  name: 'SvgVisualization',
  setup() {
    const webPlugin = useWebPlugin();
    return {
      ...webPlugin
    }
  },
  computed: {
    ekostat_1_ts() {
      const param = this.deviceParam('ekostat_1', 'current_temperature_setpoint');
      return (param?.currentValue?.valueEnd?.toFixed(2) ?? '---')
          + (param?.currentValue?.valueUnit ?? '');
    },
    ekostat_1_ta() {
      const param = this.deviceParam('ekostat_1', 'temperature_actual');
      return (param?.currentValue?.valueEnd?.toFixed(2) ?? '---')
          + (param?.currentValue?.valueUnit ?? '');
    },
    ekostat_1_state() {
      const param = this.deviceParam('ekostat_1', 'relay_state');
      return (param?.currentValue?.valueEnd ?? '---')
    },
  },
})
</script>

Then reference them in the SVG template:

<text x="100" y="80" font-family="monospace" font-size="16">
  Setpoint: {{ ekostat_1_ts }}
</text>
<text x="100" y="110" font-family="monospace" font-size="16">
  Actual: {{ ekostat_1_ta }}
</text>
<text x="100" y="140" font-family="monospace" font-size="16">
  Relay: {{ ekostat_1_state }}
</text>

The corresponding enms-plugin-manifest.json for the example above uses deviceParams to load all three parameters at once by their codes:

{
  "inputs": [
    {
      "inputId": "ekostat_1",
      "type": "device",
      "inputName": { "pl": "Urządzenie ekostat" },
      "valueSpec": {
        "type": "device",
        "params": [
          {
            "type": "deviceParams",
            "paramDsl": "param_code = current_temperature_setpoint,temperature_actual,relay_state",
            "valueSpec": {
              "type": "param",
              "currentValue": {
                "value": true
              }
            }
          }
        ]
      }
    }
  ]
}

With deviceParams, matching parameters are automatically keyed by their parameter code in the payload, so deviceParam('ekostat_1', 'temperature_actual') works without declaring each parameter separately (see Manifest Format for details).


emitClick

emitClick(action, input, mouseEvent) sends a click event to the enms-web host. The host interprets the action and the input payload to perform a context-specific response (e.g. opening a tooltip or navigating to a detail page).

Parameters:

| Parameter | Type | Description | |---|---|---| | action | PluginEventAction | Action for the host to perform. Currently supported: 'open-tooltip' | | input | InputPayload \| InputPayload[] \| null | The input payload associated with the click. Determines what the host shows (e.g. which device/parameter tooltip). Pass a device payload for device-level actions or a parameter payload for parameter-level actions. | | mouseEvent | MouseEvent | The native DOM mouse event ($event in Vue templates). Used to position the tooltip at the click location. |

Example – open tooltip for a device:

When the user clicks a shape, the host opens a tooltip showing details for the device:

<rect x="40" y="60" width="320" height="90"
      style="cursor: pointer"
      v-on:click="emitClick('open-tooltip', V?.device1, $event)" />

Example – open tooltip for a specific parameter:

Clicking a parameter value area opens a tooltip for that specific parameter:

<rect x="40" y="168" width="148" height="80"
      style="cursor: pointer"
      v-on:click="emitClick('open-tooltip', V?.device1?.params?.active_power, $event)" />

Example – using deviceParam in a click handler:

<rect x="40" y="168" width="148" height="80"
      style="cursor: pointer"
      v-on:click="emitClick('open-tooltip', deviceParam('ekostat_1', 'temperature_actual'), $event)" />

The call is safe even when the host is not connected – if the plugin is running in standalone mode, emitClick is silently ignored.


Plugin State

Plugins can persist arbitrary state across page reloads and widget reconfigurations using the stateStorage / pluginState API. This is useful for things like zoom levels, user preferences, or any configuration data that should survive a page refresh.

Saving state

Call stateStorage.value?.saveState(state) with any serializable object. In connected mode the state is persisted via the ENMS host; in standalone mode it falls back to localStorage.

It is good practice to guard saves so they only happen when the plugin is in edit mode or standalone mode, preventing unintended changes during normal view-mode operation:

import { defineComponent } from 'vue'
import { useWebPlugin } from '@enms-vis/enms-web-plugin-vue'

type PluginState = { zoom: number; activeTab: string }

export default defineComponent({
  name: 'SvgVisualization',
  setup() {
    return { ...useWebPlugin<PluginState>('MyPlugin') }
  },
  methods: {
    saveCurrentState() {
      if (!this.editMode && !this.isStandalone) return; // view-only guard
      this.stateStorage?.saveState({ zoom: 2, activeTab: 'overview' });
    },
  },
})

Reading state

pluginState is a computed Ref that always reflects the latest value from the active storage backend. It has three possible values:

| Value | Meaning | |---|---| | undefined | Not yet received – waiting for the first host config delivery. | | null | Received; no state has been saved yet. | | TState | The previously saved state object. |

React to state changes with a watcher:

watch: {
  pluginState: {
    handler(state: PluginState | null | undefined) {
      if (!state) return; // null or undefined – nothing to restore
      this.pluginLog('info', 'Restoring state:', state);
      // apply state to your visualisation...
    },
    immediate: true, // run once the first value is delivered
  },
},

Or read it synchronously inside computed properties:

computed: {
  currentZoom() {
    return this.pluginState?.zoom ?? 1;
  },
},

Storage backends

stateStorage is a ShallowRef that switches backend automatically depending on the connection status:

| Mode | Backend | Where state lives | |---|---|---| | Connected (iframe) | HostStateStorage | ENMS host saveState API | | Standalone / dev | LocalStateStorage | Browser localStorage |

stateStorage is null before the first connection attempt completes (i.e. briefly during the initial onMounted). Always guard with ?. when accessing it outside of lifecycle callbacks.


Runtime Info, Debug Mode & Logging

useWebPlugin exposes several reactive properties that reflect the current runtime context delivered by the host:

| Property | Type | Description | |---|---|---| | debugMode | Ref<boolean> | true when the host has enabled debug mode for this widget. | | editMode | Ref<boolean> | true when the dashboard is in edit mode. Use this to guard state saves or show configuration overlays. | | isStandalone | Ref<boolean> | true when the plugin is running outside of an ENMS host iframe (e.g. during local development). | | widgetName | Ref<string \| null> | Human-readable name of the widget instance as configured in the dashboard. Useful for labelling or logging. | | enmsVersion | Ref<string \| null> | Version string of the running ENMS application. Useful for diagnostics. |

You can use these properties directly in templates:

<!-- Show a configuration hint when in edit mode -->
<text v-if="editMode" x="10" y="20" font-size="12" fill="blue">
  Edit mode – configure inputs in the sidebar
</text>

<!-- Show widget name as a title -->
<text x="10" y="40" font-size="14">{{ widgetName ?? 'Unnamed widget' }}</text>

<!-- Show ENMS version in a debug overlay -->
<text v-if="debugMode" x="10" y="310" font-size="9" fill="grey">
  enms {{ enmsVersion }} · standalone: {{ isStandalone }}
</text>

Use pluginLog for diagnostics. Debug-level messages are only printed when the host enables debug mode.

this.pluginLog('info', 'Initialization complete');
this.pluginLog('debug', 'Current state:', this.V);
this.pluginLog('warn', 'Missing expected input');

You can conditionally render debug overlays in the SVG template:

<text v-if="debugMode" x="10" y="290" font-size="10" fill="red">
  DEBUG: {{ JSON.stringify(V) }}
</text>

Using Without Vue

The tooling is set up primarily to work with Vue, but it is not required. Visualizations may be created with any framework (or none at all) as long as they:

  1. Include an enms-plugin-manifest.json in the package root.
  2. Use the @enms-vis/enms-web-plugin library to communicate with the host.

Example using plain TypeScript (no Vue):

import { WebPluginHost } from '@enms-vis/enms-web-plugin'

const host = new WebPluginHost();

host.onInputUpdate((payload) => {
  // Update DOM directly
  const el = document.getElementById(payload.inputId);
  if (el && payload.type === 'param') {
    el.textContent = String(payload.currentValue?.valueEnd ?? '–');
  }
});

host.onConfigUpdate((config) => {
  console.log('Debug mode:', config.debugMode);
});

host.connect();

The @enms-vis/enms-web-plugin-vue package is a convenience wrapper – it is not loaded or required by enms-web at runtime. Only enms-plugin-manifest.json and the built output matter.


Manifest Format (enms-plugin-manifest.json)

The manifest file is the contract between a visualization plugin and enms-web. It tells enms-web what data the plugin needs so it can build the configuration UI and deliver the correct input payloads at runtime.

Top-level structure

{
  "indexPath": "dist/index.html",
  "inputs": [ ... ]
}

| Field | Required | Description | |---|---|---| | indexPath | No | Path to the plugin's entry HTML file relative to the package root. Defaults to dist/index.html. | | inputs | Yes | Array of input definitions. Each input has a unique inputId that becomes a key in the reactive V state. |

Input types

Every input object has a type that determines what data it represents and how enms-web resolves it.

device – Device input

Provides a DeviceInputPayload containing device metadata and (optionally) its parameters.

{
  "inputId": "boiler_1",
  "type": "device",
  "required": true,
  "inputName": { "en": "Main boiler", "pl": "Kocioł główny" },
  "description": { "en": "The boiler whose data will be displayed." },
  "valueSpec": {
    "type": "device",
    "params": [ ... ]
  }
}

| Field | Description | |---|---| | inputId | Unique key – becomes V.boiler_1 in the plugin. | | required | If true, the user must assign a device. The plugin should still handle missing values gracefully. | | inputName | Localized human-readable label shown in the configuration UI (MlText – object with locale keys). | | description | Localized description shown in the configuration UI. | | deviceDsl | Optional default DSL expression to pre-select a device. | | multiple | If true, the user can assign multiple devices; the payload becomes an array. | | valueSpec.params | Array of parameter definitions to load for the device (see below). | | valueSpec.deviceExtraFields | Optional list of extra device fields to include (e.g. "serialNumber", "notes", "deviceCfg"). |

Device parameters: deviceParams vs param

Inside valueSpec.params you can use two kinds of entries:

deviceParams – matches multiple parameters by DSL in the context of the device. Each matched parameter is automatically keyed by its parameter code. This is the most common and concise approach:

{
  "type": "deviceParams",
  "paramDsl": "param_code = current_temperature_setpoint,temperature_actual,relay_state",
  "valueSpec": {
    "type": "param",
    "currentValue": { "value": true }
  }
}

param – declares a single named parameter with its own inputId. Use this when you need a specific inputId that differs from the parameter code, or when each parameter requires a different valueSpec:

{
  "inputId": "active_power",
  "type": "param",
  "paramDsl": "param_code = 'active_power'",
  "valueSpec": {
    "type": "param",
    "currentValue": { "value": true, "valueUnit": "W" }
  }
}
currentValue field spec (AggFieldSpec)

Controls which measurement fields are included in ParamInputPayload.currentValue:

| Field | Type | Description | |---|---|---| | value | boolean | Include value fields: valueEnd, valueBegin, avg, min, max, stddev, increase, decrease, delta. | | valueUnit | string | Target unit for value fields (e.g. "kWh"). If not set, the parameter's native unit is used. | | derivative | boolean | Include derivative fields: derivativeMin, derivativeMax, derivativeAvg, derivativeStddev. | | derivativeUnit | string | Target unit for derivative fields. | | integral | boolean | Include integral field. | | integralUnit | string | Target unit for integral. | | stats | boolean | Include statistics: count, origin, quality, avgIntegrity, deltaIntegrity. | | formatTs | boolean | Include human-readable timestamps: formattedTs, formattedTsEnd, formattedTsRange, formattedModifiedTs. |

param – Standalone parameter input

Provides a ParamInputPayload for a parameter that is not scoped to a specific device input.

{
  "inputId": "outdoor_temp",
  "type": "param",
  "paramDsl": "param_code = 'outdoor_temperature'",
  "valueSpec": {
    "type": "param",
    "currentValue": { "value": true }
  }
}

number, text, boolean – Primitive inputs

Primitive inputs let the user configure the plugin's behaviour (e.g. thresholds, labels, feature flags). They do not reference any device or parameter.

{ "inputId": "temp_threshold", "type": "number", "defaultValue": 22.0,
  "inputName": { "en": "Temperature threshold" } }

{ "inputId": "title_label", "type": "text", "defaultValue": "Heating overview",
  "inputName": { "en": "Widget title" } }

{ "inputId": "show_labels", "type": "boolean", "defaultValue": true,
  "inputName": { "en": "Show labels" } }

Access in the template:

<text v-if="V?.show_labels?.value">{{ V?.title_label?.value ?? '' }}</text>

Full example

{
  "inputs": [
    {
      "inputId": "ekostat_1",
      "type": "device",
      "inputName": { "pl": "Urządzenie ekostat" },
      "valueSpec": {
        "type": "device",
        "params": [
          {
            "type": "deviceParams",
            "paramDsl": "param_code = current_temperature_setpoint,temperature_actual,relay_state",
            "valueSpec": {
              "type": "param",
              "currentValue": {
                "value": true
              }
            }
          }
        ]
      }
    },
    {
      "inputId": "temp_threshold",
      "type": "number",
      "defaultValue": 22.0,
      "inputName": { "en": "Temperature threshold", "pl": "Próg temperatury" }
    }
  ]
}

Building for enms-web

When a visualization plugin is deployed to enms-web, the following structure is expected:

my-visualization/
├── enms-plugin-manifest.json    ← plugin input definitions (required)
└── dist/                        ← built output directory (default)
    ├── index.html               ← entry point loaded in the iframe
    ├── assets/
    │   ├── index-XXXXX.js       ← bundled application code
    │   └── index-XXXXX.css      ← bundled styles
    └── …

This layout is fully compatible with the default Vite build outputnpm run build produces the dist/ directory with index.html and hashed assets out of the box. The only addition on top of a standard Vite project is the enms-plugin-manifest.json file in the package root.

enms-plugin-manifest.json

The manifest is the contract between the visualization and enms-web. It declares what inputs the plugin needs. enms-web reads it to build the widget configuration UI and to know what data to deliver. See Manifest Format for the full specification.

Key fields:

  • inputs – array of input definitions. Each input has an inputId, a type (device, param, number, text, boolean), and an optional valueSpec describing what data fields to include.
  • indexPath (optional) – path to the entry HTML file relative to the package root. Defaults to dist/index.html if not specified.

dist directory

The dist/ directory is produced by npm run build (Vite build). It contains the compiled index.html and all bundled assets. enms-web serves the contents of this directory inside a sandboxed iframe.

index.html

The entry point loaded by enms-web inside the iframe. It must mount the application and establish the plugin connection (handled automatically when using the Vue scaffolding).

Package files

The package.json files field controls what gets published:

{
  "files": [
    "dist/**/*",
    "enms-plugin-manifest.json"
  ]
}

This ensures only the built output and the manifest are included in the deployable package.