@bonsae/nrg 0.1.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.
Files changed (70) hide show
  1. package/README.md +130 -0
  2. package/build/server/index.cjs +910 -0
  3. package/build/server/resources/nrg-client.js +6530 -0
  4. package/build/server/resources/vue.esm-browser.prod.js +13 -0
  5. package/build/vite/index.js +1893 -0
  6. package/build/vite/utils.js +60 -0
  7. package/package.json +110 -0
  8. package/src/core/client/api/index.ts +17 -0
  9. package/src/core/client/app.vue +201 -0
  10. package/src/core/client/components/node-red-config-input.vue +57 -0
  11. package/src/core/client/components/node-red-editor-input.vue +283 -0
  12. package/src/core/client/components/node-red-input.vue +71 -0
  13. package/src/core/client/components/node-red-json-schema-form.vue +369 -0
  14. package/src/core/client/components/node-red-select-input.vue +86 -0
  15. package/src/core/client/components/node-red-typed-input.vue +130 -0
  16. package/src/core/client/components.d.ts +18 -0
  17. package/src/core/client/globals.d.ts +17 -0
  18. package/src/core/client/index.ts +504 -0
  19. package/src/core/client/shims-vue.d.ts +5 -0
  20. package/src/core/client/tsconfig.json +18 -0
  21. package/src/core/client/virtual.d.ts +5 -0
  22. package/src/core/constants.ts +18 -0
  23. package/src/core/server/index.ts +209 -0
  24. package/src/core/server/nodes/config-node.ts +67 -0
  25. package/src/core/server/nodes/index.ts +4 -0
  26. package/src/core/server/nodes/io-node.ts +178 -0
  27. package/src/core/server/nodes/node.ts +255 -0
  28. package/src/core/server/nodes/types/config-node.ts +28 -0
  29. package/src/core/server/nodes/types/index.ts +3 -0
  30. package/src/core/server/nodes/types/io-node.ts +37 -0
  31. package/src/core/server/nodes/types/node.ts +41 -0
  32. package/src/core/server/nodes/utils.ts +83 -0
  33. package/src/core/server/schemas/base.ts +66 -0
  34. package/src/core/server/schemas/index.ts +3 -0
  35. package/src/core/server/schemas/type.ts +95 -0
  36. package/src/core/server/schemas/types/index.ts +73 -0
  37. package/src/core/server/tsconfig.json +17 -0
  38. package/src/core/server/types/index.ts +73 -0
  39. package/src/core/server/utils.ts +56 -0
  40. package/src/core/server/validator.ts +32 -0
  41. package/src/core/validator.ts +222 -0
  42. package/src/tsconfig/base.json +23 -0
  43. package/src/tsconfig/client.json +11 -0
  44. package/src/tsconfig/server.json +6 -0
  45. package/src/vite/async-utils.ts +61 -0
  46. package/src/vite/client/build.ts +223 -0
  47. package/src/vite/client/index.ts +1 -0
  48. package/src/vite/client/plugins/html-generator.ts +75 -0
  49. package/src/vite/client/plugins/index.ts +5 -0
  50. package/src/vite/client/plugins/locales-generator.ts +126 -0
  51. package/src/vite/client/plugins/minifier.ts +22 -0
  52. package/src/vite/client/plugins/node-definitions-inliner.ts +224 -0
  53. package/src/vite/client/plugins/static-copy.ts +43 -0
  54. package/src/vite/defaults.ts +77 -0
  55. package/src/vite/errors.ts +37 -0
  56. package/src/vite/index.ts +3 -0
  57. package/src/vite/logger.ts +94 -0
  58. package/src/vite/node-red-launcher.ts +344 -0
  59. package/src/vite/plugin.ts +61 -0
  60. package/src/vite/plugins/build.ts +73 -0
  61. package/src/vite/plugins/index.ts +2 -0
  62. package/src/vite/plugins/server.ts +267 -0
  63. package/src/vite/server/build.ts +124 -0
  64. package/src/vite/server/index.ts +1 -0
  65. package/src/vite/server/plugins/index.ts +3 -0
  66. package/src/vite/server/plugins/output-wrapper.ts +109 -0
  67. package/src/vite/server/plugins/package-json-generator.ts +203 -0
  68. package/src/vite/server/plugins/type-generator.ts +285 -0
  69. package/src/vite/types.ts +369 -0
  70. package/src/vite/utils.ts +103 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "noImplicitAny": false,
12
+ "noImplicitOverride": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["**/*.ts", "../constants.ts", "../validator.ts"],
16
+ "exclude": ["node_modules"]
17
+ }
@@ -0,0 +1,73 @@
1
+ import type { TObject } from "@sinclair/typebox";
2
+
3
+ interface RED {
4
+ _: (key: string, substitutions?: Record<string, string>) => string;
5
+ log: {
6
+ info(msg: any): void;
7
+ warn(msg: any): void;
8
+ error(message: string, error: any): void;
9
+ debug(msg: any): void;
10
+ trace(msg: any): void;
11
+ };
12
+ nodes: {
13
+ registerType: (type: string, def: any, opts?: any) => void;
14
+ getNode: <T = any>(id: string) => T | undefined;
15
+ createNode: (node: any, config: Record<string, any>) => void;
16
+ getCredentials: (id: string) => Record<string, any> | undefined;
17
+ };
18
+ httpAdmin: {
19
+ get(path: string, handler: (req: any, res: any) => void): void;
20
+ post(path: string, handler: (req: any, res: any) => void): void;
21
+ put(path: string, handler: (req: any, res: any) => void): void;
22
+ delete(path: string, handler: (req: any, res: any) => void): void;
23
+ };
24
+ util: {
25
+ evaluateNodeProperty(
26
+ value: any,
27
+ type: string,
28
+ node: any,
29
+ msg: Record<string, any> | undefined,
30
+ callback: (err: Error | null, result: any) => void,
31
+ ): void;
32
+ };
33
+ settings: Record<string, any>;
34
+ }
35
+
36
+ interface NodeRedContextStore {
37
+ get(
38
+ key: string,
39
+ store: string | undefined,
40
+ callback: (err: Error | null, value: any) => void,
41
+ ): void;
42
+ set(
43
+ key: string,
44
+ value: any,
45
+ store: string | undefined,
46
+ callback: (err: Error | null) => void,
47
+ ): void;
48
+ keys(
49
+ store: string | undefined,
50
+ callback: (err: Error | null, keys: string[]) => void,
51
+ ): void;
52
+ }
53
+
54
+ interface NodeDefinitionApiResponse {
55
+ type: string;
56
+ align?: "left" | "right";
57
+ category?: "config" | string;
58
+ color?: `#${string}`;
59
+ icon?: string;
60
+ labelStyle?: "node_label" | "node_label_italic" | string;
61
+ paletteLabel?: string;
62
+ inputs?: number;
63
+ outputs?: number;
64
+ inputLabels?: string | string[];
65
+ outputLabels?: string | string[];
66
+ configSchema: TObject | null;
67
+ credentialsSchema: TObject | null;
68
+ inputSchema?: TObject | null;
69
+ outputsSchema?: TObject | null;
70
+ settingsSchema?: TObject | null;
71
+ }
72
+
73
+ export { RED, NodeDefinitionApiResponse, NodeRedContextStore };
@@ -0,0 +1,56 @@
1
+ import type { TObject, SchemaOptions } from "@sinclair/typebox";
2
+
3
+ interface NodeSchemaOptions extends SchemaOptions {
4
+ "node-type"?: string;
5
+ default?: any;
6
+ format?: string;
7
+ }
8
+
9
+ function getDefaultsFromSchema(
10
+ schema: TObject,
11
+ ): Record<string, { type?: string; required: boolean; value: any }> {
12
+ const result: Record<
13
+ string,
14
+ { type?: string; required: boolean; value: any }
15
+ > = {};
16
+
17
+ for (const [key, value] of Object.entries(schema.properties)) {
18
+ // NOTE: these are excluded from defaults because they must be set by the editor
19
+ if (["x", "y", "z", "g", "wires", "type", "id"].includes(key)) continue;
20
+
21
+ const property = value as NodeSchemaOptions;
22
+
23
+ result[key] = {
24
+ // NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
25
+ required: false,
26
+ value: property.default ?? undefined,
27
+ // NOTE: I'm using a custom json schema keyword to determine the node type
28
+ type: property["node-type"],
29
+ };
30
+ }
31
+
32
+ return result;
33
+ }
34
+
35
+ function getCredentialsFromSchema(
36
+ schema: TObject,
37
+ ): Record<string, { type: string; required: boolean; value: any }> {
38
+ const result: Record<
39
+ string,
40
+ { type: string; required: boolean; value: any }
41
+ > = {};
42
+
43
+ for (const [key, value] of Object.entries(schema.properties)) {
44
+ const property = value as NodeSchemaOptions;
45
+ result[key] = {
46
+ // NOTE: required is always false because it is controlled by the JSON Schema and AJV validation instead of using node-red client core
47
+ required: false,
48
+ type: property.format === "password" ? "password" : "text",
49
+ value: property.default ?? undefined,
50
+ };
51
+ }
52
+
53
+ return result;
54
+ }
55
+
56
+ export { getDefaultsFromSchema, getCredentialsFromSchema };
@@ -0,0 +1,32 @@
1
+ import { Validator } from "../validator";
2
+ import type { RED } from "./types";
3
+
4
+ class NodeRedValidator extends Validator {
5
+ constructor(RED: RED) {
6
+ super({
7
+ customKeywords: [
8
+ { keyword: "skip-validation", schemaType: "boolean", valid: true },
9
+ {
10
+ keyword: "node-type",
11
+ type: "string",
12
+ validate: (schemaValue: string, dataValue: string) => {
13
+ if (!dataValue) return true;
14
+ const node = RED.nodes.getNode(dataValue);
15
+ return node?.type === schemaValue;
16
+ },
17
+ },
18
+ ],
19
+ customFormats: {
20
+ "node-id": /^[a-zA-Z0-9-_]+$/,
21
+ "flow-id": /^[a-f0-9]{16}$/,
22
+ "topic-path": (data: string) => /^[a-zA-Z0-9/_-]+$/.test(data),
23
+ },
24
+ });
25
+ }
26
+ }
27
+
28
+ export let validator: NodeRedValidator = undefined!;
29
+
30
+ export function initValidator(RED: RED): void {
31
+ validator = new NodeRedValidator(RED);
32
+ }
@@ -0,0 +1,222 @@
1
+ import type {
2
+ Options,
3
+ ErrorObject,
4
+ ErrorsTextOptions,
5
+ AnySchemaObject,
6
+ ValidateFunction,
7
+ KeywordDefinition,
8
+ } from "ajv";
9
+ import Ajv from "ajv";
10
+ import addFormats from "ajv-formats";
11
+ import addErrors from "ajv-errors";
12
+
13
+ interface ValidationResult<T = unknown> {
14
+ valid: boolean;
15
+ data?: T;
16
+ errors?: ErrorObject[];
17
+ errorMessage?: string;
18
+ }
19
+
20
+ interface ValidatorOptions extends Options {
21
+ customKeywords?: string[] | KeywordDefinition[];
22
+ customFormats?: Record<string, RegExp | ((data: string) => boolean)>;
23
+ }
24
+
25
+ interface DetailedError {
26
+ field: string;
27
+ message: string;
28
+ keyword: string;
29
+ params: Record<string, unknown>;
30
+ schemaPath: string;
31
+ }
32
+
33
+ interface ValidateOption {
34
+ cacheKey?: string;
35
+ throwOnError?: boolean;
36
+ }
37
+
38
+ class Validator {
39
+ private readonly ajv: Ajv;
40
+
41
+ public constructor(options?: ValidatorOptions) {
42
+ const { customKeywords, customFormats, ...ajvOptions } = options || {};
43
+
44
+ this.ajv = new Ajv({
45
+ allErrors: true,
46
+ code: {
47
+ source: false,
48
+ },
49
+ coerceTypes: true,
50
+ removeAdditional: false,
51
+ strict: false,
52
+ strictSchema: false,
53
+ useDefaults: true,
54
+ validateFormats: true,
55
+ // NOTE: typebox handles validation via typescript
56
+ // NOTE: if true, types that are not serializable JSON, like Function, would not work
57
+ validateSchema: false,
58
+ verbose: true,
59
+ ...ajvOptions,
60
+ });
61
+
62
+ addFormats(this.ajv);
63
+ addErrors(this.ajv);
64
+
65
+ this.addCustomKeywords(customKeywords || []);
66
+ this.addCustomFormats(customFormats || {});
67
+ }
68
+
69
+ /**
70
+ * Add custom keywords to the validator
71
+ */
72
+ private addCustomKeywords(keywords?: string[] | KeywordDefinition[]): void {
73
+ if (!keywords) return;
74
+ keywords.forEach((keyword) => {
75
+ this.ajv.addKeyword(keyword);
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Add custom formats to the validator
81
+ */
82
+ private addCustomFormats(
83
+ formats?: Record<string, RegExp | ((data: string) => boolean)>,
84
+ ): void {
85
+ if (!formats) return;
86
+
87
+ Object.entries(formats).forEach(([name, validator]) => {
88
+ if (validator instanceof RegExp) {
89
+ this.ajv.addFormat(name, validator);
90
+ } else {
91
+ this.ajv.addFormat(name, { validate: validator });
92
+ }
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Create a validator function with caching
98
+ * @param schema - JSON Schema to validate against
99
+ * @param cacheKey - Optional cache key for reusing validators
100
+ */
101
+ public createValidator(
102
+ schema: AnySchemaObject,
103
+ cacheKey?: string,
104
+ ): ValidateFunction {
105
+ if (cacheKey && !schema.$id) {
106
+ schema.$id = cacheKey;
107
+ }
108
+
109
+ if (schema.$id) {
110
+ const cached = this.ajv.getSchema(schema.$id);
111
+ if (cached) return cached;
112
+ }
113
+
114
+ const validator = this.ajv.compile(schema);
115
+
116
+ return validator;
117
+ }
118
+
119
+ /**
120
+ * Validate data against a schema and return a structured result
121
+ */
122
+ public validate<T = unknown>(
123
+ data: unknown,
124
+ schema: AnySchemaObject,
125
+ options?: ValidateOption,
126
+ ): ValidationResult<T> {
127
+ const validator = this.createValidator(schema, options?.cacheKey);
128
+ const valid = validator(data);
129
+
130
+ if (!valid) {
131
+ const errorMessage = this.formatErrors(validator.errors);
132
+
133
+ if (options?.throwOnError) {
134
+ throw new ValidationError(errorMessage, validator.errors || []);
135
+ }
136
+
137
+ return {
138
+ valid: false,
139
+ errors: validator.errors || undefined,
140
+ errorMessage,
141
+ };
142
+ }
143
+
144
+ return {
145
+ valid: true,
146
+ data: data as T,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Format errors into a human-readable string
152
+ */
153
+ public formatErrors(
154
+ errors?: ErrorObject[] | null,
155
+ options?: ErrorsTextOptions,
156
+ ): string {
157
+ if (!errors || errors.length === 0) {
158
+ return "No errors";
159
+ }
160
+
161
+ return this.ajv.errorsText(errors, {
162
+ separator: "; ",
163
+ dataVar: "data",
164
+ ...options,
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Get detailed error information
170
+ */
171
+ public getDetailedErrors(errors?: ErrorObject[] | null): DetailedError[] {
172
+ if (!errors || errors.length === 0) return [];
173
+
174
+ return errors.map((error) => ({
175
+ field: error.instancePath || "/",
176
+ message: error.message || "Validation failed",
177
+ keyword: error.keyword,
178
+ params: error.params,
179
+ schemaPath: error.schemaPath,
180
+ }));
181
+ }
182
+
183
+ /**
184
+ * Add a schema to the validator for reference
185
+ */
186
+ public addSchema(schema: AnySchemaObject, key?: string): this {
187
+ this.ajv.addSchema(schema, key);
188
+ return this;
189
+ }
190
+
191
+ /**
192
+ * Remove a schema from the validator
193
+ */
194
+ public removeSchema(key: string): this {
195
+ this.ajv.removeSchema(key);
196
+ return this;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Custom error class for validation errors
202
+ */
203
+ class ValidationError extends Error {
204
+ constructor(
205
+ message: string,
206
+ public readonly errors: ErrorObject[],
207
+ ) {
208
+ super(message);
209
+ this.name = "ValidationError";
210
+ Object.setPrototypeOf(this, ValidationError.prototype);
211
+ }
212
+ }
213
+
214
+ const validator = new Validator();
215
+
216
+ export { Validator, ValidationError, validator };
217
+ export type {
218
+ ValidationResult,
219
+ ValidatorOptions,
220
+ ValidateOption,
221
+ DetailedError,
222
+ };
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": false,
4
+ "allowSyntheticDefaultImports": true,
5
+ "checkJs": false,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "module": "ESNext",
9
+ "moduleResolution": "bundler",
10
+ "noEmit": true,
11
+ "noImplicitAny": false,
12
+ "noImplicitOverride": true,
13
+ "noImplicitReturns": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "resolveJsonModule": true,
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "stripInternal": true,
20
+ "target": "ES2022"
21
+ },
22
+ "exclude": ["node_modules", "dist"]
23
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"]
5
+ },
6
+ "files": [
7
+ "../core/client/shims-vue.d.ts",
8
+ "../core/client/components.d.ts",
9
+ "../core/client/globals.d.ts"
10
+ ]
11
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "./base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022"]
5
+ }
6
+ }
@@ -0,0 +1,61 @@
1
+ function debounce<T extends (...args: any[]) => any>(
2
+ fn: T,
3
+ delay: number,
4
+ ): (...args: Parameters<T>) => void {
5
+ let timeout: NodeJS.Timeout | null = null;
6
+
7
+ return (...args: Parameters<T>) => {
8
+ if (timeout) clearTimeout(timeout);
9
+ timeout = setTimeout(() => fn(...args), delay);
10
+ };
11
+ }
12
+
13
+ function withTimeout<T>(
14
+ promise: Promise<T>,
15
+ ms: number,
16
+ fallback?: T,
17
+ ): Promise<T> {
18
+ return new Promise((resolve, reject) => {
19
+ const timeout = setTimeout(() => {
20
+ if (fallback !== undefined) {
21
+ resolve(fallback);
22
+ } else {
23
+ reject(new Error(`Timeout after ${ms}ms`));
24
+ }
25
+ }, ms);
26
+
27
+ promise
28
+ .then((result) => {
29
+ clearTimeout(timeout);
30
+ resolve(result);
31
+ })
32
+ .catch((error) => {
33
+ clearTimeout(timeout);
34
+ reject(error);
35
+ });
36
+ });
37
+ }
38
+
39
+ async function retry<T>(
40
+ fn: () => Promise<T>,
41
+ options: { attempts?: number; delay?: number } = {},
42
+ ): Promise<T> {
43
+ const { attempts = 3, delay = 1000 } = options;
44
+
45
+ let lastError: Error | undefined;
46
+
47
+ for (let i = 0; i < attempts; i++) {
48
+ try {
49
+ return await fn();
50
+ } catch (error) {
51
+ lastError = error as Error;
52
+ if (i < attempts - 1) {
53
+ await new Promise((resolve) => setTimeout(resolve, delay));
54
+ }
55
+ }
56
+ }
57
+
58
+ throw lastError;
59
+ }
60
+
61
+ export { debounce, withTimeout, retry };