node-red-contrib-typescript 1.0.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/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "node-red-contrib-typescript",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "author": "kezziny",
6
+ "license": "ISC",
7
+ "node-red" : {
8
+ "nodes": {
9
+ "instance": "src/instance/instance.js"
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,270 @@
1
+ <script type="text/javascript">
2
+ var renderStreamData = function (id, data, node) {
3
+ let $chart = document.getElementById("data-view-output-chart-" + id);
4
+ if (!$chart) {
5
+ const $container = document.getElementById(id)
6
+ if (!$container) { return }
7
+ const text = document.createElementNS("http://www.w3.org/2000/svg", 'text')
8
+ text.setAttribute('id', `data-view-output-text-${id}`)
9
+ text.setAttribute('x', `0`)
10
+ text.setAttribute('y', `45`)
11
+ text.setAttribute('text-anchor', `start`)
12
+ $container.insertBefore(text, $container.lastChild.nextSibling);
13
+ // Creat group
14
+ const group = document.createElementNS("http://www.w3.org/2000/svg", 'g');
15
+ group.setAttribute('id', `data-view-output-chart-${id}`);
16
+ group.setAttribute('innerHTML', `data-view-output-chart-${id}`);
17
+ $container.insertBefore(group, $container.lastChild.nextSibling);
18
+ $chart = $container
19
+ }
20
+
21
+ let $text = document.getElementById(`data-view-output-text-${id}`);
22
+ $text.textContent = ''
23
+ let x = ``
24
+ if (data.value === undefined) {
25
+ x = "";
26
+ } else if (typeof data.value == `object`) {
27
+ x = JSON.stringify(data.value, null, ' ')
28
+ } else {
29
+ x = data.value + ''
30
+ }
31
+ var
32
+ lines = x.split('\n'),
33
+ tn,
34
+ ts
35
+
36
+ lines.forEach(function (value, index) {
37
+ let countSpace = value.length - value.trim().length
38
+ tn = document.createTextNode(value);
39
+ ts = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
40
+ ts.setAttribute('dy', (index) ? "1.2em" : "1.2em"); // : 0
41
+ ts.setAttribute('x', (countSpace * 10) + 25);
42
+ ts.setAttribute('text-anchor', 'start');
43
+ ts.setAttribute('fill', '#555');
44
+ ts.setAttribute('stroke', 'transparent');
45
+ ts.setAttribute('font-size', `11`)
46
+ ts.appendChild(tn);
47
+ $text.append(ts);
48
+ });
49
+ }
50
+
51
+ function streamSubscriptions(event, data) {
52
+ if (data.hasOwnProperty("data")) {
53
+ let node = RED.nodes.node(data.id);
54
+ renderStreamData(data.id, data.data, node);
55
+ }
56
+ }
57
+
58
+ class TSEditor {
59
+ constructor(node, path) {
60
+ this.node = node;
61
+
62
+ const mainModel = monaco.editor.createModel(
63
+ node.code || "",
64
+ 'typescript',
65
+ window.monaco.Uri.parse('file://' + path)
66
+ );
67
+
68
+ this.editor = RED.editor.createEditor({
69
+ id: "node-input-example-editor",
70
+ mode: 'ace/mode/typescript',
71
+ value: node.code || "",
72
+ theme: "vs-dark",
73
+ focus: true,
74
+ model: mainModel,
75
+ });
76
+
77
+ window.monaco.editor.getModels()[0].dispose();
78
+
79
+ window.monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
80
+ "paths": {
81
+ "/*": [
82
+ "file:///*"
83
+ ],
84
+ "#*": [
85
+ "file:///lib/*"
86
+ ]
87
+ },
88
+ experimentalDecorators: true,
89
+ moduleResolution: window.monaco.languages.typescript.ModuleResolutionKind.NodeJs,
90
+ allowNonTsExtensions: true,
91
+ module: monaco.languages.typescript.ModuleKind.ESNext,
92
+ target: monaco.languages.typescript.ScriptTarget.ESNext
93
+ });
94
+ }
95
+
96
+ recalculateGlobal(subflowId) {
97
+ let nodes = [];
98
+ let subflowNodes = [];
99
+ let workspaces = [];
100
+ let subflows = [];
101
+
102
+ RED.nodes.eachWorkspace(item => workspaces.push(item));
103
+ RED.nodes.eachSubflow(item => subflows.push(item));
104
+ RED.nodes.eachNode(item => {
105
+ if (item.type === "typescript")
106
+ nodes.push(item);
107
+ if (item.type.startsWith("subflow:"))
108
+ subflowNodes.push(item);
109
+ });
110
+
111
+ function resolveFlow(id) {
112
+ let imports = {};
113
+ nodes
114
+ .filter(node => node.z === id)
115
+ .forEach(node => {
116
+ imports[`/${encodeURIComponent(node.name)}.ts`] = node.code;
117
+ });
118
+ subflowNodes
119
+ .filter(node => node.z === id)
120
+ .forEach(node => {
121
+ let subImports = resolveFlow(node.type.split(":")[1]);
122
+ for (let key in subImports) {
123
+ imports[`/${encodeURIComponent(node.name)}${key}`] = subImports[key];
124
+ }
125
+ });
126
+ return imports;
127
+ }
128
+
129
+
130
+ let imports = {};
131
+ workspaces.forEach(workspace => {
132
+ let subImports = resolveFlow(workspace.id);
133
+ for (let key in subImports) {
134
+ if (key.indexOf("//") === -1)
135
+ imports[`/${encodeURIComponent(workspace.label)}${key}`] = subImports[key];
136
+ }
137
+ });
138
+
139
+ if (subflowId) {
140
+ let subflow = subflows.find(s => s.id === subflowId);
141
+ let subImports = resolveFlow(subflowId);
142
+ for (let key in subImports) {
143
+ if (key.indexOf("//") === -1)
144
+ imports[`/subflow${key}`] = subImports[key];
145
+ }
146
+ }
147
+
148
+ for (let key in imports) {
149
+ window.monaco.languages.typescript.typescriptDefaults.addExtraLib(imports[key], "file://" + key);
150
+ }
151
+
152
+ window.monaco.languages.typescript.typescriptDefaults.addExtraLib(`
153
+ let node = {
154
+ error: (msg:any) => {},
155
+ send: (msg:any) => {},
156
+ on: (topic:"close"|"input", callback:any) => {},
157
+ status: (status:{fill?:"red"|"yellow"|"green"|"blue"|"grey", shape?:"dot"|"ring", text?:string}) => {},
158
+ log: (data:any) => {}
159
+ };
160
+ let flow = {
161
+ get: (topic:string):any => {};
162
+ set: (topic:string, value:any) => {};
163
+ };
164
+ let global = {
165
+ get: (topic:string):any => {};
166
+ set: (topic:string, value:any) => {};
167
+ };
168
+ let context = {
169
+ get: (topic:string):any => {};
170
+ set: (topic:string, value:any) => {};
171
+ };
172
+ let env = {
173
+ get: (topic:string):any => {};
174
+ set: (topic:string, value:any) => {};
175
+ };
176
+ `, 'global.d.ts'
177
+ );
178
+ }
179
+
180
+ getCode() {
181
+ return this.editor.getValue();
182
+ }
183
+
184
+ destroy() {
185
+ this.editor.destroy();
186
+ }
187
+ }
188
+ </script>
189
+ <script type="text/javascript">
190
+ RED.nodes.registerType('typescript',{
191
+ category: 'function',
192
+ color: '#3178c6',
193
+ defaults: {
194
+ name: {value:""},
195
+ outputs:{value:1},
196
+ code: {value:""},
197
+ },
198
+ inputs:1,
199
+ outputs:1,
200
+ icon: "function.svg",
201
+ label: function () {
202
+ RED.comms.unsubscribe('stream-status-' + this.id, streamSubscriptions);
203
+ RED.comms.subscribe('stream-status-' + this.id, streamSubscriptions);
204
+ return this.name || "typescript";
205
+ },
206
+ oneditprepare: function() {
207
+ let path = "/__editor__.tsx";
208
+
209
+ let workspaces = [];
210
+ let subflows = [];
211
+
212
+ RED.nodes.eachWorkspace(item => workspaces.push(item));
213
+ RED.nodes.eachSubflow(item => subflows.push(item));
214
+
215
+ let target = this;
216
+ if (workspaces.some(wf => wf.id === target.z)) {
217
+ target = workspaces.find(wf => wf.id === target.z);
218
+ path = `/${target.label}${path}`;
219
+ } else if (subflows.some(wf => wf.id === target.z)) {
220
+ target = subflows.find(wf => wf.id === target.z);
221
+ path = `/subflow${path}`;
222
+ }
223
+
224
+ this.view = {
225
+ editor: new TSEditor(this, path),
226
+ };
227
+
228
+ this.view.editor.recalculateGlobal(path.startsWith("/subflow") ? this.z : null);
229
+
230
+ $( "#node-input-outputs" ).spinner({
231
+ min: 0,
232
+ max: 500,
233
+ change: function(event, ui) {
234
+ var value = parseInt(this.value);
235
+ value = isNaN(value) ? 1 : value;
236
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
237
+ value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
238
+ if (value !== this.value) { $(this).spinner("value", value); }
239
+ }
240
+ });
241
+ },
242
+ oneditsave: function() {
243
+ this.code = this.view.editor.getCode();
244
+ this.view.editor.destroy();
245
+ delete this.view;
246
+ },
247
+ oneditcancel: function() {
248
+ this.view.editor.destroy();
249
+ delete this.view;
250
+ }
251
+ });
252
+ </script>
253
+
254
+ <script type="text/html" data-template-name="typescript">
255
+ <div class="form-row">
256
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
257
+ <input type="text" id="node-input-name" placeholder="Name">
258
+ </div>
259
+
260
+ <div class="form-row">
261
+ <label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
262
+ <input id="node-input-outputs" style="width: 60px;" value="1">
263
+ </div>
264
+
265
+ <div style="height: calc(100% - 68px); min-height:250px; width: 800px" class="node-text-editor" id="node-input-example-editor"></div>
266
+ </script>
267
+
268
+ <script type="text/html" data-help-name="typescript">
269
+ <p>A simple node that converts the message payloads into all lower-case characters</p>
270
+ </script>
@@ -0,0 +1,157 @@
1
+ let registry = {};
2
+ module.exports = function(RED) {
3
+ var helper = require("../../helper.js");
4
+ var vm = require("vm");
5
+
6
+ function TypescriptInstance(config) {
7
+ RED.nodes.createNode(this,config);
8
+ var node = this;
9
+ node.config = config;
10
+ node.libs = config.libs;
11
+ node.modules = config.modules;
12
+ node.code = config.code;
13
+
14
+ var sandbox = helper.createSandbox(RED, node);
15
+ let moduleErrors = false;
16
+
17
+ // region Registry
18
+ let tabs = {};
19
+ RED.nodes.eachNode(n => {
20
+ if (n.type === "tab") tabs[n.id] = n;
21
+ });
22
+
23
+ let path = "/";
24
+ let segment = RED.nodes.getNode(node.z);
25
+ if (segment === null) {
26
+ path = `/${tabs[node.z].label}${path}`;
27
+ }
28
+ while(segment !== null) {
29
+ if (segment.node.name === undefined) {
30
+ path = `/${segment.node.id}${path}`;
31
+ } else {
32
+ path = `/${segment.node.name}${path}`;
33
+ }
34
+ let parent = RED.nodes.getNode(segment.node.z);
35
+ if (parent === null) {
36
+ path = `/${tabs[segment.node.z].label}${path}`;
37
+ }
38
+ segment = parent;
39
+ }
40
+ registry[path + node.config.name] = node;
41
+ node.on("close", () => {delete registry[path + node.config.name]});
42
+
43
+ if (node.config.name === "index") {
44
+ let pkgPath = path.substring(0, path.length - 1);
45
+ registry[pkgPath] = node;
46
+ node.on("close", () => {delete registry[pkgPath]});
47
+ }
48
+ // endregion
49
+
50
+ // region Dependencies
51
+ let modules = {};
52
+ let libraryPromises = [];
53
+ const regexp = /require\("(?<lib>.*)"\)/gm;
54
+ for (const match of helper.transpile(config.code).matchAll(regexp)) {
55
+ let target = match[1];
56
+ if (target.startsWith("/subflow")) {
57
+ target = path + target.substring(9);
58
+ }
59
+ if (target.startsWith("#")) {
60
+ target = "/lib/" + target.substring(1);
61
+ }
62
+ if (target.startsWith("$/")) {
63
+ target = target.substring(1);
64
+ }
65
+ if (target.startsWith("./")) {
66
+ target = path + target.substring(2);
67
+ }
68
+ if (target.endsWith(".pkg")) {
69
+ target = target.substring(0, target.length - 4) + "/index";
70
+ }
71
+
72
+ if (target.startsWith("/")) {
73
+ libraryPromises.push(
74
+ new Promise(async (resolve, reject) => {
75
+ let dependency = registry[target];
76
+ for(let i = 0; i < 100 && (dependency === null || dependency === undefined); i++) {
77
+ dependency = registry[target];
78
+ await new Promise(r => setTimeout(r, 50));
79
+ }
80
+ if (dependency === null || dependency === undefined) reject(new Error("Dependency not found: " + target));
81
+ else {
82
+ dependency.loading.then(() => resolve(dependency)).catch(error => reject(error));
83
+ }
84
+ })
85
+ );
86
+ } else {
87
+ libraryPromises.push(RED.import(target).then(lib => {
88
+ modules[target] = lib.default;
89
+ }).catch(err => {
90
+ node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()}))
91
+ throw err;
92
+ }));
93
+ }
94
+ }
95
+ // endregion
96
+
97
+ node.loading = new Promise((resolve, reject) => {
98
+ Promise.all(libraryPromises).then(() => {
99
+ var context = vm.createContext(sandbox);
100
+ node.exports = context.exports;
101
+
102
+ context.require = (target) => {
103
+ if (target.startsWith("/subflow")) {
104
+ target = path + target.substring(9);
105
+ }
106
+ if (target.startsWith("#")) {
107
+ target = "/lib/" + target.substring(1);
108
+ }
109
+ if (target.startsWith("$/")) {
110
+ target = target.substring(1);
111
+ }
112
+ if (target.startsWith("./")) {
113
+ target = path + target.substring(2);
114
+ }
115
+ if (target.endsWith(".pkg")) {
116
+ target = target.substring(0, target.length - 4) + "/index";
117
+ }
118
+
119
+ if (target.startsWith("/")) {
120
+ return registry[target].exports;
121
+ }
122
+ return modules[target];
123
+ };
124
+
125
+ try {
126
+ var iniOpt = helper.createVMOpt(node, " setup");
127
+ var iniScript = helper.createScript(node, iniOpt, config.code);
128
+ context.__initSend__ = function(msgs) { node.send(msgs); };
129
+ promise = iniScript.runInContext(context, iniOpt)
130
+ .then(() => resolve(node))
131
+ .catch(e => {
132
+ node.error(e);
133
+ node.error(e.stack);
134
+ node.status({fill: 'red', shape: 'dot', text: 'error'})
135
+ });
136
+
137
+ node.on("close", function() {
138
+ while (node.outstandingTimers.length > 0) {
139
+ clearTimeout(node.outstandingTimers.pop());
140
+ }
141
+ while (node.outstandingIntervals.length > 0) {
142
+ clearInterval(node.outstandingIntervals.pop());
143
+ }
144
+ });
145
+ }
146
+ catch(err) {
147
+ node.error(err);
148
+ node.error(err.stack);
149
+ }
150
+ }).catch(error => reject(error))
151
+ }).catch(error => {
152
+ node.error(error);
153
+ node.error(error.stack);
154
+ });
155
+ }
156
+ RED.nodes.registerType("typescript", TypescriptInstance);
157
+ }
package/test.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare namespace Test {
2
+ declare class Cucc {}
3
+ }