@bonsae/nrg 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,7 @@
11
11
 
12
12
  # nrg
13
13
 
14
- Build Node-RED nodes with Vue 3, TypeScript, JSON Schema validations, Vite and Vistest.
15
-
14
+ Build Node-RED nodes with Vue 3, TypeScript, JSON Schema validations, Vite and Vitest.
16
15
 
17
16
  ## Package Exports
18
17
 
@@ -20,9 +19,21 @@ Build Node-RED nodes with Vue 3, TypeScript, JSON Schema validations, Vite and V
20
19
  | --- | --- |
21
20
  | `@bonsae/nrg` | Root entry — `defineRuntimeSettings` |
22
21
  | `@bonsae/nrg/server` | Server node classes, schema utilities, validation (`IONode`, `ConfigNode`, `defineIONode`, `defineConfigNode`, `defineModule`, `SchemaType`, `defineSchema`, `Infer`) |
23
- | `@bonsae/nrg/client` | Client-side registration (`registerTypes`, `defineNode`) |
22
+ | `@bonsae/nrg/client` | Client-side registration (`registerTypes`, `defineNode`, `useFormNode`, `Infer`) |
24
23
  | `@bonsae/nrg/vite` | Vite plugin for building and developing Node-RED packages |
25
- | `@bonsae/nrg/tsconfig/*` | Shared TypeScript configurations for consumers |
24
+ | `@bonsae/nrg/test/server/unit` | Server unit test helpers (`createNode`, `createRED`, `MockRED`) |
25
+ | `@bonsae/nrg/test/client/unit` | Client unit test config and mocks (`defaultConfig`, `createRED`, `createJQuery`) |
26
+ | `@bonsae/nrg/test/client/unit/setup` | Setup file that installs `RED` and `$` mocks on `window` |
27
+ | `@bonsae/nrg/test/client/component` | Client component test helpers (`createNode`, `defaultConfig`, `createRED`, `createJQuery`) |
28
+ | `@bonsae/nrg/test/client/component/setup` | Setup file that installs `RED` and `$` mocks on `window` with Vue i18n |
29
+ | `@bonsae/nrg/test/client/e2e` | Browser E2E test helpers (`NodeRedEditor`, `NodeRedField`, `setup`, `teardown`) |
30
+ | `@bonsae/nrg/tsconfig/base.json` | Base TypeScript configuration |
31
+ | `@bonsae/nrg/tsconfig/core/server.json` | Core server source tsconfig |
32
+ | `@bonsae/nrg/tsconfig/core/client.json` | Core client source tsconfig |
33
+ | `@bonsae/nrg/tsconfig/test/server/unit.json` | Server unit test tsconfig |
34
+ | `@bonsae/nrg/tsconfig/test/client/unit.json` | Client unit test tsconfig |
35
+ | `@bonsae/nrg/tsconfig/test/client/component.json` | Client component test tsconfig |
36
+ | `@bonsae/nrg/tsconfig/test/client/e2e.json` | Client E2E test tsconfig |
26
37
 
27
38
  ## Quick Start
28
39
 
@@ -32,7 +43,7 @@ pnpm add @bonsae/nrg
32
43
  pnpm add -D vite vue
33
44
  ```
34
45
 
35
- > `vite` ane `vue` are dev dependencies because they are only needed at build time. Vue is included as a dependency of nrg and served automatically at runtime.
46
+ > `vite` and `vue` are dev dependencies because they are only needed at build time. Vue is included as a dependency of nrg and served automatically at runtime.
36
47
 
37
48
  **vite.config.ts**
38
49
 
@@ -55,7 +66,7 @@ export const ConfigsSchema = defineSchema(
55
66
  name: SchemaType.String({ default: "" }),
56
67
  prefix: SchemaType.String({ default: "hello" }),
57
68
  },
58
- { $id: "my-node:configs" }
69
+ { $id: "my-node:configs" },
59
70
  );
60
71
  ```
61
72
 
@@ -95,7 +106,7 @@ type Config = Infer<typeof ConfigsSchema>;
95
106
  type Input = Infer<typeof InputSchema>;
96
107
  type Output = Infer<typeof OutputSchema>;
97
108
 
98
- export default class MyNode extends IONode<Config, any, Input, Output> {
109
+ export default class MyNode extends IONode<Config, never, Input, Output> {
99
110
  static readonly type = "my-node";
100
111
  static readonly category = "function";
101
112
  static readonly color: `#${string}` = "#ffffff";
@@ -131,16 +142,18 @@ See the [consumer template](https://github.com/AllanOricil/node-red-vue-template
131
142
 
132
143
  ## Testing
133
144
 
134
- Test your nodes' server-side logic with `@bonsae/nrg/test`:
145
+ NRG provides four test libraries:
135
146
 
136
- ```bash
137
- pnpm add -D vitest
138
- ```
147
+ - `@bonsae/nrg/test/server/unit` — server-side unit tests
148
+ - `@bonsae/nrg/test/client/unit` — client-side unit tests (TypeScript logic)
149
+ - `@bonsae/nrg/test/client/component` — client component tests (Vue + browser)
150
+ - `@bonsae/nrg/test/client/e2e` — browser E2E tests (Playwright)
151
+
152
+ ### Server Unit Tests
139
153
 
140
154
  ```typescript
141
- // tests/my-node.test.ts
142
155
  import { describe, it, expect } from "vitest";
143
- import { createNode } from "@bonsae/nrg/test";
156
+ import { createNode } from "@bonsae/nrg/test/server/unit";
144
157
  import MyNode from "../src/server/nodes/my-node";
145
158
 
146
159
  describe("my-node", () => {
@@ -156,18 +169,98 @@ describe("my-node", () => {
156
169
  });
157
170
  ```
158
171
 
159
- ```bash
160
- npx vitest run
172
+ ### Client Unit Tests
173
+
174
+ Test client-side TypeScript logic (validation, utilities) with mocked `RED` and `$` globals:
175
+
176
+ ```typescript
177
+ // vitest.config.ts
178
+ import { defineConfig } from "vitest/config";
179
+ import { defaultConfig } from "@bonsae/nrg/test/client/unit";
180
+
181
+ export default defineConfig({
182
+ test: {
183
+ ...defaultConfig,
184
+ },
185
+ });
186
+ ```
187
+
188
+ ```typescript
189
+ // tests/client/unit/my-util.test.ts
190
+ import { describe, it, expect } from "vitest";
191
+ import { myUtil } from "../src/client/my-util";
192
+
193
+ describe("myUtil", () => {
194
+ it("works with RED globals", () => {
195
+ expect(myUtil("input")).toBe("expected");
196
+ });
197
+ });
161
198
  ```
162
199
 
200
+ ### Client Component Tests
201
+
202
+ Test your Vue editor components with mocked Node-RED globals:
203
+
204
+ ```typescript
205
+ // vitest.config.ts
206
+ import { defineConfig } from "vitest/config";
207
+ import { playwright } from "@vitest/browser-playwright";
208
+ import vue from "@vitejs/plugin-vue";
209
+ import { defaultConfig } from "@bonsae/nrg/test/client/component";
210
+
211
+ export default defineConfig({
212
+ plugins: [vue()],
213
+ test: {
214
+ ...defaultConfig,
215
+ browser: {
216
+ ...defaultConfig.browser,
217
+ provider: playwright(),
218
+ },
219
+ },
220
+ });
221
+ ```
222
+
223
+ ```typescript
224
+ // tests/client/component/my-component.test.ts
225
+ import { describe, test, expect, vi } from "vitest";
226
+ import { render } from "vitest-browser-vue";
227
+ import { createNode } from "@bonsae/nrg/test/client/component";
228
+ import MyComponent from "../src/client/components/my-component.vue";
229
+
230
+ describe("MyComponent", () => {
231
+ test("renders with node props", async () => {
232
+ const { node } = createNode({ name: "test" });
233
+ const screen = render(MyComponent, {
234
+ props: { node },
235
+ });
236
+ await expect.element(screen.getByText("test")).toBeInTheDocument();
237
+ });
238
+
239
+ test("calls RED.editor API", async () => {
240
+ const { node, RED } = createNode();
241
+ render(MyComponent, { props: { node, value: "" } });
242
+ expect(RED.editor.createEditor).toHaveBeenCalled();
243
+ });
244
+ });
245
+ ```
246
+
247
+ See the [testing guide](https://bonsaedev.github.io/nrg/guide/testing) for full API reference.
248
+
163
249
  ## Development
164
250
 
165
251
  ```bash
166
252
  pnpm install
167
- pnpm build # build all (server CJS, client ESM, vite plugin)
168
- pnpm typecheck # type-check server and client
169
- pnpm lint # eslint
170
- pnpm format # prettier
253
+ pnpm build # build all (server, client, vite plugin, test libs)
254
+ pnpm validate # type-check + lint + format check
255
+ pnpm validate:tsc # type-check all tsconfigs
256
+ pnpm validate:lint # eslint
257
+ pnpm validate:format # prettier check
258
+ pnpm test # run all tests
259
+ pnpm test:core:server:unit # server unit tests
260
+ pnpm test:core:client:unit # client unit tests
261
+ pnpm test:core:client:component # client component tests
262
+ pnpm test:core:client:e2e # client E2E tests
263
+ pnpm docs:dev # start docs dev server
171
264
  ```
172
265
 
173
266
  ## License
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
5
5
  "author": "Allan Oricil <allanoricil@duck.com>",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
+ "homepage": "https://bonsaedev.github.io/nrg/",
8
9
  "repository": {
9
10
  "url": "https://github.com/bonsaedev/nrg",
10
11
  "type": "git"
@@ -16,6 +17,19 @@
16
17
  "node": ">=20.19",
17
18
  "pnpm": ">=10.11.0"
18
19
  },
20
+ "keywords": [
21
+ "node-red",
22
+ "vue",
23
+ "vue3",
24
+ "typescript",
25
+ "json-schema",
26
+ "vite",
27
+ "vitest",
28
+ "iot",
29
+ "automation",
30
+ "low-code",
31
+ "framework"
32
+ ],
19
33
  "exports": {
20
34
  ".": {
21
35
  "types": "./types/index.d.ts",
@@ -33,13 +47,31 @@
33
47
  "types": "./types/vite.d.ts",
34
48
  "default": "./vite/index.js"
35
49
  },
36
- "./test": {
37
- "types": "./types/test.d.ts",
38
- "default": "./test/index.js"
50
+ "./test/server/unit": {
51
+ "types": "./types/test-server-unit.d.ts",
52
+ "default": "./test/server/unit/index.js"
53
+ },
54
+ "./test/client/component": {
55
+ "types": "./types/test-client-component.d.ts",
56
+ "default": "./test/client/component/index.js"
57
+ },
58
+ "./test/client/component/setup": "./test/client/component/setup.js",
59
+ "./test/client/unit": {
60
+ "types": "./types/test-client-unit.d.ts",
61
+ "default": "./test/client/unit/index.js"
62
+ },
63
+ "./test/client/unit/setup": "./test/client/unit/setup.js",
64
+ "./test/client/e2e": {
65
+ "types": "./types/test-client-e2e.d.ts",
66
+ "default": "./test/client/e2e/index.js"
39
67
  },
40
68
  "./tsconfig/base.json": "./tsconfig/base.json",
41
- "./tsconfig/client.json": "./tsconfig/client.json",
42
- "./tsconfig/server.json": "./tsconfig/server.json"
69
+ "./tsconfig/core/server.json": "./tsconfig/core/server.json",
70
+ "./tsconfig/core/client.json": "./tsconfig/core/client.json",
71
+ "./tsconfig/test/server/unit.json": "./tsconfig/test/server/unit.json",
72
+ "./tsconfig/test/client/component.json": "./tsconfig/test/client/component.json",
73
+ "./tsconfig/test/client/unit.json": "./tsconfig/test/client/unit.json",
74
+ "./tsconfig/test/client/e2e.json": "./tsconfig/test/client/e2e.json"
43
75
  },
44
76
  "peerDependencies": {
45
77
  "vite": "^6.0.0",
package/server/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  ErrorPortSchema: () => ErrorPortSchema,
36
36
  IONode: () => IONode,
37
37
  Node: () => Node,
38
+ NodeSourceSchema: () => NodeSourceSchema,
38
39
  NrgError: () => NrgError,
39
40
  SchemaType: () => SchemaType,
40
41
  StatusPortSchema: () => StatusPortSchema,
@@ -449,6 +450,11 @@ var IONode = class extends Node {
449
450
  `outputsSchema record key "${key}" in ${this.type} looks numeric. Use descriptive string names (e.g. "success", "failure") to avoid JavaScript object key ordering issues.`
450
451
  );
451
452
  }
453
+ if (key === "error" || key === "complete" || key === "status") {
454
+ throw new NrgError(
455
+ `outputsSchema record key "${key}" in ${this.type} is reserved for built-in ports. Use a different name (e.g. "failed" instead of "error").`
456
+ );
457
+ }
452
458
  }
453
459
  return keys.length;
454
460
  }
@@ -579,10 +585,11 @@ var IONode = class extends Node {
579
585
  }
580
586
  this.log("Output is valid");
581
587
  }
588
+ const out = Array.isArray(msg) ? msg.slice(0, this.baseOutputs) : msg;
582
589
  if (this.#send) {
583
- this.#send(msg);
590
+ this.#send(out);
584
591
  } else {
585
- this.node.send(msg);
592
+ this.node.send(out);
586
593
  }
587
594
  }
588
595
  // --- Built-in port management ---
@@ -599,16 +606,20 @@ var IONode = class extends Node {
599
606
  }
600
607
  /**
601
608
  * Send a message to a specific output port by index or name.
602
- * Built-in port `"status"` is resolved automatically based on the node's
603
- * built-in port configuration.
604
609
  * Custom named ports are resolved from `outputsSchema` when it is a record.
605
610
  * Numeric indices refer to the base output ports (0-based).
606
611
  *
607
- * Note: `"error"` and `"complete"` ports are managed by the framework and
608
- * cannot be sent to directly. Throw an error to trigger the error port,
609
- * and the complete port is sent automatically on successful input processing.
612
+ * Built-in ports (`"error"`, `"complete"`, `"status"`) are managed by the
613
+ * framework and cannot be sent to directly. Use `this.status()` for status,
614
+ * throw an error or call `this.error()` for the error port, and the complete
615
+ * port is sent automatically on successful input processing.
610
616
  */
611
617
  sendToPort(port, msg) {
618
+ if (port === "error" || port === "complete" || port === "status") {
619
+ throw new NrgError(
620
+ `sendToPort("${port}") is not allowed. Built-in ports are managed by the framework.`
621
+ );
622
+ }
612
623
  this.#sendToPort(port, msg);
613
624
  }
614
625
  #sendToPort(port, msg) {
@@ -652,10 +663,9 @@ var IONode = class extends Node {
652
663
  name: this.name
653
664
  };
654
665
  }
655
- status(status, data) {
666
+ status(status) {
656
667
  this.node.status(status);
657
668
  this.#sendToPort("status", {
658
- ...data,
659
669
  status,
660
670
  source: this.#nodeSource()
661
671
  });
@@ -1185,21 +1195,24 @@ var CompletePortSchema = SchemaType.Object({
1185
1195
  })
1186
1196
  });
1187
1197
  var StatusPortSchema = SchemaType.Object({
1188
- status: SchemaType.Object({
1189
- fill: SchemaType.Optional(
1190
- SchemaType.Union([
1191
- SchemaType.Literal("red"),
1192
- SchemaType.Literal("green")
1193
- ])
1194
- ),
1195
- shape: SchemaType.Optional(
1196
- SchemaType.Union([
1197
- SchemaType.Literal("dot"),
1198
- SchemaType.Literal("string")
1199
- ])
1200
- ),
1201
- text: SchemaType.Optional(SchemaType.String())
1202
- }),
1198
+ status: SchemaType.Union([
1199
+ SchemaType.Object({
1200
+ fill: SchemaType.Optional(
1201
+ SchemaType.Union([
1202
+ SchemaType.Literal("red"),
1203
+ SchemaType.Literal("green")
1204
+ ])
1205
+ ),
1206
+ shape: SchemaType.Optional(
1207
+ SchemaType.Union([
1208
+ SchemaType.Literal("dot"),
1209
+ SchemaType.Literal("string")
1210
+ ])
1211
+ ),
1212
+ text: SchemaType.Optional(SchemaType.String())
1213
+ }),
1214
+ SchemaType.String()
1215
+ ]),
1203
1216
  source: NodeSourceSchema
1204
1217
  });
1205
1218
 
@@ -1214,6 +1227,7 @@ function defineModule(definition) {
1214
1227
  ErrorPortSchema,
1215
1228
  IONode,
1216
1229
  Node,
1230
+ NodeSourceSchema,
1217
1231
  NrgError,
1218
1232
  SchemaType,
1219
1233
  StatusPortSchema,