Warning
While NRG is at v0, breaking changes can land in any release and will not bump the major version. Pin an exact version and review the release notes before upgrading.
Build Node-RED nodes with Vue 3, TypeScript, JSON Schemas, Vite and Vitest.
Scaffold a new project with everything wired up:
pnpm create @bonsae/nrgA node is a config schema (drives the editor form + validation) and a class (the logic). Here's a small greeting node — a few typed fields and one output:
src/shared/schemas/greeting.ts
import { SchemaType, defineSchema } from "@bonsae/nrg/schema";
export const ConfigsSchema = defineSchema(
{
greeting: SchemaType.String({ default: "Hello", description: "The greeting word placed before the name.", "x-nrg-form": { icon: "comment" } }),
style: SchemaType.Union(
[SchemaType.Literal("plain"), SchemaType.Literal("excited"), SchemaType.Literal("friendly")],
{ default: "plain", description: "Tone of the greeting.", "x-nrg-form": { icon: "paint-brush" } },
),
repeat: SchemaType.Number({ default: 1, description: "How many times to repeat the greeting.", "x-nrg-form": { icon: "repeat" } }),
note: SchemaType.Optional(
SchemaType.String({ default: "", description: "Optional note shown under the node.", "x-nrg-form": { icon: "pencil" } }),
),
},
{ $id: "GreetingConfigsSchema" },
);src/server/nodes/greeting.ts
import { IONode, type Infer, type Input, type Outputs, type Port } from "@bonsae/nrg/server";
import { ConfigsSchema } from "@/schemas/greeting";
type Config = Infer<typeof ConfigsSchema>;
type GreetingInput = Input<Port<{ name: string }>>;
type GreetingOutputs = Outputs<{ greeting: Port<{ text: string }> }>;
export default class Greeting extends IONode<Config, never, GreetingInput, GreetingOutputs> {
static override readonly type = "greeting";
static override readonly configSchema = ConfigsSchema;
override async input(msg: GreetingInput) {
const { greeting, style, repeat } = this.config;
const suffix = style === "excited" ? "!" : style === "friendly" ? " :)" : "";
const text = Array.from({ length: repeat }, () => `${greeting}, ${msg.name}${suffix}`).join(" ");
this.send("greeting", { text });
}
}You wrote no editor HTML, no jQuery, no oneditprepare — nrg generates the entire edit dialog from the schema and types above: the style union becomes a dropdown, strings and numbers become validated inputs, every non-Optional field is marked required with a * (so greeting, style, and repeat get one; note, wrapped in Optional, doesn't), and the input/output ports and lifecycle wiring come for free.
A classic Node-RED node hand-writes hundreds of lines of HTML for its edit form and keeps it in sync with the runtime by hand. Here it's derived from your schema and types — so it can't drift, and every field is validated for free.
See the documentation for the full walkthrough — schemas, the generated editor form, testing, and building — or the node-red-salesforce repo for a real-world reference.
MIT
