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

@redkern/node-red-kit

v1.0.1

Published

Shared runtime library for redkern Node-RED palettes

Readme

@redkern/node-red-kit

Shared runtime library for redkern Node-RED palettes. It registers no node types, editor resources, or runtime plugins by itself and has no runtime dependencies.

Status: Stable release, version 1.0.1. Implemented: init/bind/plugin, naming, config, secrets, auth, admin/internal HTTP, metrics source lifecycle, Redis client factory, resilience, async, logging, and lifecycle.

Requirements

  • Node.js 22 or newer
  • Node-RED runtime APIs are used through the RED object supplied to the palette

Initialize a palette

Call init once in the palette module closure. Every node constructor must call RED.nodes.createNode before bind:

const kit = require('@redkern/node-red-kit');

module.exports = function (RED) {
  const rk = kit.init(RED, {
    domain: 'gateway',
    settingsType: 'redkern-gateway-config'
  });

  function PrefixNode(config) {
    RED.nodes.createNode(this, config);
    const k = rk.bind(this);
    const parsed = k.configure(config, {
      prefix: { type: 'str', default: '' }
    });

    k.onInput(async (msg, send) => {
      if (!parsed.ok) throw new kit.ConfigError(parsed.errors);
      if (typeof msg.payload !== 'string') {
        throw new kit.ConfigError([
          { field: 'payload', code: 'INVALID_TYPE', message: 'Expected string' }
        ]);
      }
      msg.payload = parsed.value.prefix + msg.payload;
      send(msg);
    }, { concurrency: 8, maxQueue: 800 });
  }

  RED.nodes.registerType('redkern-gateway-prefix', PrefixNode);
};

The input handler receives (msg, send) only. Kit owns Node-RED's native done callback, calls it once after success, and calls done(error) after a rejected handler. It forwards the provided send callback and does not JSON-clone messages. Async startup must be registered before onInput; when onStart exists, startQueueTimeoutMs is required.

Configuration

init(RED, options) returns { names, config, enabled, disabledReason, bind(node), plugin({ id }), createRouteRegistry(route) }. Palette parameters resolve from non-empty REDKERN_<DOMAIN>_<NAME>, then the Node-RED custom setting derived from settingsType, then the schema default. Empty environment/settings values fall through. Invalid selected values do not fall through and disable the palette before I/O.

parseConfig(config, schema) returns { ok: true, value } or { ok: false, errors }; there is no partial value on failure. Supported schema types are int, float, bool, str, list, and enum. readEnv(name) accepts only a complete REDKERN_* variable name. readSettings(RED, settingsType, schema) reads only schema-allowlisted fields using Node-RED's type-prefixed camelCase setting names.

Node fields are read only from the flow config. Kit does not re-expand values from process.env. Node-RED whole-property ${NAME} placeholders that remain unresolved become safe config errors.

Secrets

Declare credentials in the palette's editor definition, then read them with k.readSecret(field, { legacyConfig }). An explicitly present empty credential means cleared and does not revive a legacy value. Legacy fallback is opt-in and emits a one-time warning. Use k.requireSecret(value, label) before onStart when a value is mandatory. k.secret(value) returns an idempotent release function and also releases automatically when the node closes.

Secrets declared in init({ secrets }) are read from REDKERN_<DOMAIN>_<NAME>_TOKEN or the corresponding private settingsType setting. Required values must be 32-4096 printable ASCII characters without whitespace. Values are never included in rk.config; active values and sensitive fields are redacted from kit logs and error-output messages.

Async and lifecycle

k.onStart(async (attempt) => {}, { classify, startAlertMs }) retries transient failures with capped full-jitter backoff. attempt provides its number, signal, and onClose(fn, { force }). Each failed attempt is cleaned before retry.

k.onInput(handler, { concurrency, maxQueue, startQueueTimeoutMs, errorOutput, outputCount }) uses a bounded FIFO limiter. Startup deadlines begin when a message arrives. errorOutput requires an outputCount; failures go to the last output and native done() is called without an error. k.onClose(fn, { force }) adds a LIFO cleanup step under the shared close budget. Active input work drains before resource cleanup under that same deadline.

k.track(promise, { label, key }) consumes rejection and resolves to a boolean. backoff(options) returns next()/reset(). sleep(ms, signal) is abortable. retry(fn, options) invokes fn({ attempt, signal }) and retries only when its explicit retryOn(error) predicate allows it. withTimeout(promise, ms, options) stops waiting but does not cancel the underlying operation. createLimiter({ concurrency, maxQueue }) returns run, close, and stats.

Authentication and admin routes

secureCompare(expected, supplied) uses fresh-key HMAC digests and timingSafeEqual. extractToken(req) accepts an unambiguous Bearer or x-api-key header; query tokens are rejected. requireSecret(k, value, label) validates a required node secret. PublicError(status, message, { code }) represents a public 4xx response.

rk.createRouteRegistry({ method, path, permission, handler }) registers on RED.httpAdmin through RED.auth.needsPermission. Routes fail closed when adminAuth is missing or the admin API is disabled. Paths are static suffixes under rk.names.routeBase; write methods require application/json with optional UTF-8 charset. The handler receives a normalized request, response facade, and active-entry snapshot. init({ internalServer }) creates a lazy listener with exact local/token auth and per-node acquire/release leases. Cluster retry, mesh/preStop evidence, and fault-injection acceptance remain release work.

module.exports = function (RED) {
  const rk = kit.init(RED, {
    domain: 'gateway',
    internalServer: {
      port: 9552,
      routes: [{
        method: 'GET',
        path: '/health',
        auth: 'local',
        handler: async (_req, res, activeEntries) => res.json({ owners: activeEntries.size })
      }]
    }
  });

  function HealthNode(config) {
    RED.nodes.createNode(this, config);
    const k = rk.bind(this);
    const lease = k.internalServer.acquire(this.id, { node: this });
    k.track(lease.ready, { label: 'health listener' });
  }

  RED.nodes.registerType('redkern-gateway-health', HealthNode);
};

Metrics source

When init receives metrics: { contentType, metrics }, each node can call k.metrics.acquire() once and release automatically on close. The palette-level source registers on the first lease, unregisters on the last, and re-announces after redkern:metrics:discover. V1 accepts only Prometheus text 0.0.4. The metrics receiver/aggregator remains palette-owned rather than part of kit.

Redis client factory

The @redkern/node-red-kit/redis subpath exports createRedisClient, classifyRedisError, defineScripts, and hashTag. The palette passes its ioredis constructor; the kit does not install or import ioredis. The factory returns a lazy handle with client, connect(), close(), and synchronous force(). Role options disable offline command buffering and hidden command replay. Standalone is integration-tested; cluster/TLS/fault-injection acceptance is still required before stable release.

Node-RED conventions

Use the message's passed send callback and preserve unrelated message properties. Use node.send only for source-node events. Always catch asynchronous failures through the returned promise path; do not call node.error for the same failure already sent through native done(error). Status colors/shapes follow Node-RED and status text stays below 20 characters.

See the JavaScript node guide, status guide, and packaging guide. The kit remains a library: it deliberately has no node-red package metadata or node-red keyword.

Security and support

Report vulnerabilities through GitHub Private Vulnerability Reporting or to [email protected]; do not open a public issue for an unpatched vulnerability. For general support, use GitHub issues or [email protected]. See SECURITY.md.