@bonsae/nrg 0.6.0 → 0.6.1
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/build/server/index.cjs +1 -1
- package/build/vite/index.js +95 -0
- package/package.json +4 -1
- package/src/core/server/index.ts +1 -0
- package/src/core/server/nodes/factories.ts +8 -5
- package/src/core/server/nodes/types/factories.ts +17 -2
- package/src/vite/server/plugins/type-generator.ts +157 -0
package/build/server/index.cjs
CHANGED
|
@@ -683,7 +683,7 @@ function defineIONode(def) {
|
|
|
683
683
|
return def.registered?.(RED);
|
|
684
684
|
}
|
|
685
685
|
async input(msg) {
|
|
686
|
-
return def.input.call(this, msg);
|
|
686
|
+
if (def.input) return def.input.call(this, msg);
|
|
687
687
|
}
|
|
688
688
|
async created() {
|
|
689
689
|
if (def.created) return def.created.call(this);
|
package/build/vite/index.js
CHANGED
|
@@ -619,6 +619,87 @@ function getNodeTypeExports(filePath) {
|
|
|
619
619
|
}
|
|
620
620
|
return result;
|
|
621
621
|
}
|
|
622
|
+
var SCHEMA_PROP_SEMANTICS = {
|
|
623
|
+
configSchema: "ConfigSchema",
|
|
624
|
+
credentialsSchema: "CredentialsSchema",
|
|
625
|
+
inputSchema: "InputSchema",
|
|
626
|
+
outputsSchema: "OutputsSchema",
|
|
627
|
+
settingsSchema: "SettingsSchema"
|
|
628
|
+
};
|
|
629
|
+
function getSchemaReferences(filePath) {
|
|
630
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
631
|
+
const source = ts.createSourceFile(
|
|
632
|
+
filePath,
|
|
633
|
+
content,
|
|
634
|
+
ts.ScriptTarget.ESNext,
|
|
635
|
+
true
|
|
636
|
+
);
|
|
637
|
+
const importMap = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const stmt of source.statements) {
|
|
639
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause) {
|
|
640
|
+
const moduleSpecifier = stmt.moduleSpecifier.text;
|
|
641
|
+
const namedBindings = stmt.importClause.namedBindings;
|
|
642
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
643
|
+
for (const el of namedBindings.elements) {
|
|
644
|
+
importMap.set(el.name.text, moduleSpecifier);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const schemaRefs = /* @__PURE__ */ new Map();
|
|
650
|
+
function extractIdentifiers(node) {
|
|
651
|
+
if (ts.isIdentifier(node)) return [node.text];
|
|
652
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
653
|
+
return node.elements.filter(ts.isIdentifier).map((el) => el.text);
|
|
654
|
+
}
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
for (const stmt of source.statements) {
|
|
658
|
+
if (ts.isClassDeclaration(stmt)) {
|
|
659
|
+
for (const member of stmt.members) {
|
|
660
|
+
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name) && member.name.text in SCHEMA_PROP_SEMANTICS && member.initializer) {
|
|
661
|
+
const ids = extractIdentifiers(member.initializer);
|
|
662
|
+
if (ids.length > 0) {
|
|
663
|
+
schemaRefs.set(member.name.text, ids);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (ts.isExportAssignment(stmt) && stmt.expression && ts.isCallExpression(stmt.expression)) {
|
|
669
|
+
const callee = stmt.expression.expression;
|
|
670
|
+
if (ts.isIdentifier(callee) && (callee.text === "defineIONode" || callee.text === "defineConfigNode")) {
|
|
671
|
+
const arg = stmt.expression.arguments[0];
|
|
672
|
+
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
673
|
+
for (const prop of arg.properties) {
|
|
674
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text in SCHEMA_PROP_SEMANTICS) {
|
|
675
|
+
const ids = extractIdentifiers(prop.initializer);
|
|
676
|
+
if (ids.length > 0) {
|
|
677
|
+
schemaRefs.set(prop.name.text, ids);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const result = [];
|
|
686
|
+
for (const [propName, identifiers] of schemaRefs) {
|
|
687
|
+
const semanticName = SCHEMA_PROP_SEMANTICS[propName];
|
|
688
|
+
const isArray = identifiers.length > 1;
|
|
689
|
+
for (const identifier of identifiers) {
|
|
690
|
+
const importSource = importMap.get(identifier);
|
|
691
|
+
if (importSource) {
|
|
692
|
+
result.push({
|
|
693
|
+
localName: identifier,
|
|
694
|
+
semanticName: isArray ? identifier : semanticName,
|
|
695
|
+
importSource,
|
|
696
|
+
tupleProp: isArray ? semanticName : void 0
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
622
703
|
function buildNodeReexports(srcDir, entryFile) {
|
|
623
704
|
const nodesDir = path3.join(srcDir, "nodes");
|
|
624
705
|
const nodeFiles = collectTsFiles(nodesDir);
|
|
@@ -635,6 +716,20 @@ function buildNodeReexports(srcDir, entryFile) {
|
|
|
635
716
|
).join(", ");
|
|
636
717
|
lines.push(`export type { ${prefixed} } from "${specifier}";`);
|
|
637
718
|
}
|
|
719
|
+
const schemaRefs = getSchemaReferences(file);
|
|
720
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const ref of schemaRefs) {
|
|
722
|
+
const resolvedSource = path3.relative(
|
|
723
|
+
path3.dirname(entryFile),
|
|
724
|
+
path3.resolve(path3.dirname(file), ref.importSource)
|
|
725
|
+
).replace(/\\/g, "/");
|
|
726
|
+
const sourceSpecifier = resolvedSource.startsWith(".") ? resolvedSource : `./${resolvedSource}`;
|
|
727
|
+
if (!bySource.has(sourceSpecifier)) bySource.set(sourceSpecifier, []);
|
|
728
|
+
bySource.get(sourceSpecifier).push(`${ref.localName} as ${ns}${ref.semanticName}`);
|
|
729
|
+
}
|
|
730
|
+
for (const [source, names] of bySource) {
|
|
731
|
+
lines.push(`export { ${names.join(", ")} } from "${source}";`);
|
|
732
|
+
}
|
|
638
733
|
return lines.join("\n");
|
|
639
734
|
}).join("\n");
|
|
640
735
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonsae/nrg",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
"require": "./build/server/index.cjs",
|
|
47
47
|
"default": "./build/server/index.cjs"
|
|
48
48
|
},
|
|
49
|
+
"./server/nodes": {
|
|
50
|
+
"types": "./src/core/server/nodes/index.ts"
|
|
51
|
+
},
|
|
49
52
|
"./client": "./src/core/client/index.ts",
|
|
50
53
|
"./schemas": "./src/core/server/schemas/index.ts",
|
|
51
54
|
"./schemas/types": "./src/core/server/schemas/types/index.ts",
|
package/src/core/server/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
InferOutputs,
|
|
7
7
|
IONodeDefinition,
|
|
8
8
|
ConfigNodeDefinition,
|
|
9
|
+
NodeClassBase,
|
|
9
10
|
HexColor,
|
|
10
11
|
} from "./types";
|
|
11
12
|
import { IONode } from "./io-node";
|
|
@@ -25,7 +26,7 @@ function defineIONode<
|
|
|
25
26
|
TInputSchema,
|
|
26
27
|
TOutputsSchema
|
|
27
28
|
>,
|
|
28
|
-
) {
|
|
29
|
+
): NodeClassBase {
|
|
29
30
|
const NodeClass = class extends IONode<
|
|
30
31
|
InferOr<TConfigSchema, any>,
|
|
31
32
|
InferOr<TCredsSchema, any>,
|
|
@@ -65,7 +66,7 @@ function defineIONode<
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
async input(msg: InferOr<TInputSchema, any>) {
|
|
68
|
-
return def.input.call(this as any, msg);
|
|
69
|
+
if (def.input) return def.input.call(this as any, msg);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
override async created() {
|
|
@@ -84,14 +85,16 @@ function defineIONode<
|
|
|
84
85
|
configurable: true,
|
|
85
86
|
});
|
|
86
87
|
|
|
87
|
-
return NodeClass;
|
|
88
|
+
return NodeClass as unknown as NodeClassBase;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
function defineConfigNode<
|
|
91
92
|
TConfigSchema extends TSchema | undefined = undefined,
|
|
92
93
|
TCredsSchema extends TSchema | undefined = undefined,
|
|
93
94
|
TSettingsSchema extends TSchema | undefined = undefined,
|
|
94
|
-
>(
|
|
95
|
+
>(
|
|
96
|
+
def: ConfigNodeDefinition<TConfigSchema, TCredsSchema, TSettingsSchema>,
|
|
97
|
+
): NodeClassBase {
|
|
95
98
|
const NodeClass = class extends ConfigNode<
|
|
96
99
|
InferOr<TConfigSchema, any>,
|
|
97
100
|
InferOr<TCredsSchema, any>,
|
|
@@ -127,7 +130,7 @@ function defineConfigNode<
|
|
|
127
130
|
configurable: true,
|
|
128
131
|
});
|
|
129
132
|
|
|
130
|
-
return NodeClass;
|
|
133
|
+
return NodeClass as unknown as NodeClassBase;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
export { defineIONode, defineConfigNode };
|
|
@@ -79,7 +79,7 @@ interface IONodeDefinition<
|
|
|
79
79
|
>,
|
|
80
80
|
removed?: boolean,
|
|
81
81
|
): void | Promise<void>;
|
|
82
|
-
input(
|
|
82
|
+
input?(
|
|
83
83
|
this: BoundIONode<
|
|
84
84
|
TConfigSchema,
|
|
85
85
|
TCredsSchema,
|
|
@@ -112,4 +112,19 @@ interface ConfigNodeDefinition<
|
|
|
112
112
|
): void | Promise<void>;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// Return types for factory functions — uses structural typing to avoid
|
|
116
|
+
// referencing internal class paths in declaration emit, while preserving
|
|
117
|
+
// the instance type for NodeRef inference.
|
|
118
|
+
interface NodeClassBase {
|
|
119
|
+
readonly type: string;
|
|
120
|
+
readonly category: string;
|
|
121
|
+
new (...args: any[]): any;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type {
|
|
125
|
+
InferOr,
|
|
126
|
+
InferOutputs,
|
|
127
|
+
IONodeDefinition,
|
|
128
|
+
ConfigNodeDefinition,
|
|
129
|
+
NodeClassBase,
|
|
130
|
+
};
|
|
@@ -161,6 +161,139 @@ function getNodeTypeExports(
|
|
|
161
161
|
* Runtime class values are exposed by `cjsDefaultExportPlugin` dynamically
|
|
162
162
|
* via the `nodes` array on the package function.
|
|
163
163
|
*/
|
|
164
|
+
/**
|
|
165
|
+
* Semantic names for schema properties in both class static properties
|
|
166
|
+
* and factory definition objects.
|
|
167
|
+
*/
|
|
168
|
+
const SCHEMA_PROP_SEMANTICS: Record<string, string> = {
|
|
169
|
+
configSchema: "ConfigSchema",
|
|
170
|
+
credentialsSchema: "CredentialsSchema",
|
|
171
|
+
inputSchema: "InputSchema",
|
|
172
|
+
outputsSchema: "OutputsSchema",
|
|
173
|
+
settingsSchema: "SettingsSchema",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Extract schema references from a node file — works for both class-based
|
|
178
|
+
* and factory-based (defineIONode/defineConfigNode) patterns.
|
|
179
|
+
*
|
|
180
|
+
* Returns an array of { localName, semanticName, importSource } where:
|
|
181
|
+
* - localName: the identifier used in the file (e.g., "ConfigsSchema")
|
|
182
|
+
* - semanticName: the semantic slot name (e.g., "ConfigSchema")
|
|
183
|
+
* - importSource: the relative import path (e.g., "../schemas/my-node")
|
|
184
|
+
*/
|
|
185
|
+
function getSchemaReferences(
|
|
186
|
+
filePath: string,
|
|
187
|
+
): Array<{ localName: string; semanticName: string; importSource: string }> {
|
|
188
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
189
|
+
const source = ts.createSourceFile(
|
|
190
|
+
filePath,
|
|
191
|
+
content,
|
|
192
|
+
ts.ScriptTarget.ESNext,
|
|
193
|
+
true,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Build a map of imported identifiers → their source module
|
|
197
|
+
const importMap = new Map<string, string>();
|
|
198
|
+
for (const stmt of source.statements) {
|
|
199
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause) {
|
|
200
|
+
const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;
|
|
201
|
+
const namedBindings = stmt.importClause.namedBindings;
|
|
202
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
203
|
+
for (const el of namedBindings.elements) {
|
|
204
|
+
importMap.set(el.name.text, moduleSpecifier);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Collect schema property assignments: { propName → identifiers[] }
|
|
211
|
+
const schemaRefs = new Map<string, string[]>();
|
|
212
|
+
|
|
213
|
+
// Extract identifier(s) from an initializer — handles both single identifiers
|
|
214
|
+
// and array literals like [Output1Schema, Output2Schema].
|
|
215
|
+
function extractIdentifiers(node: ts.Expression): string[] {
|
|
216
|
+
if (ts.isIdentifier(node)) return [node.text];
|
|
217
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
218
|
+
return node.elements.filter(ts.isIdentifier).map((el) => el.text);
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const stmt of source.statements) {
|
|
224
|
+
// Class-based: static readonly configSchema = ConfigsSchema
|
|
225
|
+
// static readonly outputsSchema = [Output1Schema, Output2Schema]
|
|
226
|
+
if (ts.isClassDeclaration(stmt)) {
|
|
227
|
+
for (const member of stmt.members) {
|
|
228
|
+
if (
|
|
229
|
+
ts.isPropertyDeclaration(member) &&
|
|
230
|
+
ts.isIdentifier(member.name) &&
|
|
231
|
+
member.name.text in SCHEMA_PROP_SEMANTICS &&
|
|
232
|
+
member.initializer
|
|
233
|
+
) {
|
|
234
|
+
const ids = extractIdentifiers(member.initializer);
|
|
235
|
+
if (ids.length > 0) {
|
|
236
|
+
schemaRefs.set(member.name.text, ids);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Factory-based: export default defineIONode({ configSchema: X, ... })
|
|
243
|
+
if (
|
|
244
|
+
ts.isExportAssignment(stmt) &&
|
|
245
|
+
stmt.expression &&
|
|
246
|
+
ts.isCallExpression(stmt.expression)
|
|
247
|
+
) {
|
|
248
|
+
const callee = stmt.expression.expression;
|
|
249
|
+
if (
|
|
250
|
+
ts.isIdentifier(callee) &&
|
|
251
|
+
(callee.text === "defineIONode" || callee.text === "defineConfigNode")
|
|
252
|
+
) {
|
|
253
|
+
const arg = stmt.expression.arguments[0];
|
|
254
|
+
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
255
|
+
for (const prop of arg.properties) {
|
|
256
|
+
if (
|
|
257
|
+
ts.isPropertyAssignment(prop) &&
|
|
258
|
+
ts.isIdentifier(prop.name) &&
|
|
259
|
+
prop.name.text in SCHEMA_PROP_SEMANTICS
|
|
260
|
+
) {
|
|
261
|
+
const ids = extractIdentifiers(prop.initializer);
|
|
262
|
+
if (ids.length > 0) {
|
|
263
|
+
schemaRefs.set(prop.name.text, ids);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Build result: match schema references to their import sources
|
|
273
|
+
const result: Array<{
|
|
274
|
+
localName: string;
|
|
275
|
+
semanticName: string;
|
|
276
|
+
importSource: string;
|
|
277
|
+
tupleProp?: string;
|
|
278
|
+
}> = [];
|
|
279
|
+
for (const [propName, identifiers] of schemaRefs) {
|
|
280
|
+
const semanticName = SCHEMA_PROP_SEMANTICS[propName];
|
|
281
|
+
const isArray = identifiers.length > 1;
|
|
282
|
+
for (const identifier of identifiers) {
|
|
283
|
+
const importSource = importMap.get(identifier);
|
|
284
|
+
if (importSource) {
|
|
285
|
+
result.push({
|
|
286
|
+
localName: identifier,
|
|
287
|
+
semanticName: isArray ? identifier : semanticName,
|
|
288
|
+
importSource,
|
|
289
|
+
tupleProp: isArray ? semanticName : undefined,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
164
297
|
function buildNodeReexports(srcDir: string, entryFile: string): string {
|
|
165
298
|
const nodesDir = path.join(srcDir, "nodes");
|
|
166
299
|
const nodeFiles = collectTsFiles(nodesDir);
|
|
@@ -175,6 +308,7 @@ function buildNodeReexports(srcDir: string, entryFile: string): string {
|
|
|
175
308
|
|
|
176
309
|
const lines = [`export { default as ${ns} } from "${specifier}";`];
|
|
177
310
|
|
|
311
|
+
// Re-export type aliases from the node file (class-based nodes)
|
|
178
312
|
const typePairs = getNodeTypeExports(file);
|
|
179
313
|
if (typePairs.length > 0) {
|
|
180
314
|
const prefixed = typePairs
|
|
@@ -186,6 +320,29 @@ function buildNodeReexports(srcDir: string, entryFile: string): string {
|
|
|
186
320
|
lines.push(`export type { ${prefixed} } from "${specifier}";`);
|
|
187
321
|
}
|
|
188
322
|
|
|
323
|
+
// Re-export schemas referenced by the node definition (class or factory)
|
|
324
|
+
const schemaRefs = getSchemaReferences(file);
|
|
325
|
+
// Group by import source for cleaner re-exports
|
|
326
|
+
const bySource = new Map<string, string[]>();
|
|
327
|
+
for (const ref of schemaRefs) {
|
|
328
|
+
const resolvedSource = path
|
|
329
|
+
.relative(
|
|
330
|
+
path.dirname(entryFile),
|
|
331
|
+
path.resolve(path.dirname(file), ref.importSource),
|
|
332
|
+
)
|
|
333
|
+
.replace(/\\/g, "/");
|
|
334
|
+
const sourceSpecifier = resolvedSource.startsWith(".")
|
|
335
|
+
? resolvedSource
|
|
336
|
+
: `./${resolvedSource}`;
|
|
337
|
+
if (!bySource.has(sourceSpecifier)) bySource.set(sourceSpecifier, []);
|
|
338
|
+
bySource
|
|
339
|
+
.get(sourceSpecifier)!
|
|
340
|
+
.push(`${ref.localName} as ${ns}${ref.semanticName}`);
|
|
341
|
+
}
|
|
342
|
+
for (const [source, names] of bySource) {
|
|
343
|
+
lines.push(`export { ${names.join(", ")} } from "${source}";`);
|
|
344
|
+
}
|
|
345
|
+
|
|
189
346
|
return lines.join("\n");
|
|
190
347
|
})
|
|
191
348
|
.join("\n");
|