Skip to content

bonsaedev/nrg

Repository files navigation

nrg-icon

npm package build status codecov Socket Badge

nrg

Build Node-RED nodes with Vue 3, TypeScript, and JSON Schema validation.

Install

pnpm add @bonsae/nrg
pnpm add -D vite

vite is a dev dependency because it is only needed at build time. Vue is included as a dependency of nrg and served automatically at runtime.

Package Exports

Export Description
@bonsae/nrg Root entry — defineRuntimeSettings
@bonsae/nrg/server Server node classes, schema utilities, validation (IONode, ConfigNode, defineIONode, defineConfigNode, defineModule, SchemaType, defineSchema, Infer)
@bonsae/nrg/client Client-side registration (registerTypes, defineNode)
@bonsae/nrg/vite Vite plugin for building and developing Node-RED packages
@bonsae/nrg/tsconfig/* Shared TypeScript configurations for consumers

Quick Start

# In your Node-RED package project
pnpm add @bonsae/nrg
pnpm add -D vite

vite.config.ts

import { defineConfig } from "vite";
import { nodeRed } from "@bonsae/nrg/vite";

export default defineConfig({
  plugins: [nodeRed()],
});

src/server/schemas/my-node.ts

import { defineSchema, SchemaType } from "@bonsae/nrg/server";

export const ConfigsSchema = defineSchema(
  {
    name: SchemaType.String({ default: "" }),
    prefix: SchemaType.String({ default: "hello" }),
  },
  { $id: "my-node:configs" }
);

src/server/nodes/my-node.ts

NRG supports two ways to define nodes:

Functional APIClass API
import { defineIONode, SchemaType } from "@bonsae/nrg/server";
import { ConfigsSchema, InputSchema, OutputSchema } from "../schemas/my-node";

export default defineIONode({
  type: "my-node",
  color: "#ffffff",
  configSchema: ConfigsSchema,
  inputSchema: InputSchema,
  outputsSchema: OutputSchema,

  async input(msg) {
    msg.payload = `${this.config.prefix}: ${msg.payload}`;
    this.send(msg);
  },
});
import { IONode, type Schema, type Infer } from "@bonsae/nrg/server";
import { ConfigsSchema, InputSchema, OutputSchema } from "../schemas/my-node";

type Config = Infer<typeof ConfigsSchema>;
type Input = Infer<typeof InputSchema>;
type Output = Infer<typeof OutputSchema>;

export default class MyNode extends IONode<Config, any, Input, Output> {
  static readonly type = "my-node";
  static readonly category = "function";
  static readonly color: `#${string}` = "#ffffff";
  static readonly configSchema: Schema = ConfigsSchema;
  static readonly inputSchema: Schema = InputSchema;
  static readonly outputsSchema: Schema = OutputSchema;

  async input(msg: Input) {
    this.send({ payload: `${this.config.prefix}: ${msg.payload}` });
  }
}
Automatic type inference, less boilerplate Custom methods, inheritance, mixins

src/server/index.ts

import { defineModule } from "@bonsae/nrg/server";
import MyNode from "./nodes/my-node";

export default defineModule({
  nodes: [MyNode],
});

See the consumer template for a complete example.

Testing

Test your nodes' server-side logic with @bonsae/nrg/test:

pnpm add -D vitest
// tests/my-node.test.ts
import { describe, it, expect } from "vitest";
import { createNode } from "@bonsae/nrg/test";
import MyNode from "../src/server/nodes/my-node";

describe("my-node", () => {
  it("should process messages", async () => {
    const { node } = await createNode(MyNode, {
      config: { greeting: "hello" },
    });

    await node.receive({ payload: "world" });

    expect(node.sent(0)).toEqual([{ payload: "hello world" }]);
  });
});
npx vitest run

Development

pnpm install
pnpm build          # build all (server CJS, client ESM, vite plugin)
pnpm typecheck      # type-check server and client
pnpm lint           # eslint
pnpm format         # prettier

License

MIT