Following up on Monaco Editor "System-wide" code-completition and Monaco Editor "System-wide" code-completition (enums), I decided to make a subflow that will update the func.d.ts
file with types to be able to use.
A few things to start with:
- I created this to help my mind bridge between my normal work (golang) to the untyped wild that is Javascript.
- I expect @Steve-Mcl will say to not use this unless you know exactly what you're doing. I would agree.
- This only works in docker deploys.
- Seriously, don't do this unless you're willing to fiddle with things.
- It may break at any time. It's very simple but if any of the base Node-RED code changes, this will stop working and require changes.
- If anyone has advise on how to make this better, I'm all ears.
So below is a subflow where you can send typescript via msg.types
. It will then be added to func.d.ts
which, after a browser refresh, will be usable in any Function node.
Here's the help info that is part of the subflow along with some screenshots.
USAGE
Adding types
Pass in a typescript string msg.types
and it will be added to the monaco editor after a browser refresh. It does not require docker to restart.
The best way to impliment this is to have an inject node that runs on program launch going into a function node that sets the types which then pushes the value into this subflow. That way the types are
always set.
Only works in Docker deployments
Example:
msg.types = `
/**
* Represents an internal Docker service.
*/
declare class InternalDockerService {
/**
* The identifier of the worker program version.
*/
worker_programs_versions_id: number;
/**
* The version number of the worker program.
*/
version: number;
/**
* The Docker service specification.
*/
docker_service_spec: object;
}`
return msg;
Here is a quick cheat sheet on defining types:
Note that that cheat sheet is for all of typescript so the second and third columns are most applicable
to us.
Adding directly to msg
If you have a lot of locations that have values appended to the msg
object, you can override
the default NodeMessage
interface by directly assigning to it, like this. When you do this, it will automatically have the type as part of msg
. Very powerful.
msg.types = `
interface UserData {
userNumber: number;
}
interface NodeMessage {
topic?: string;
payload?: any;
_msgid?: string;
userData?: UserData;
[other: string]: any; //permit other properties
}
`
return msg
Using types
The editor doesn't enforce typing but does provide intellisense and error checking. You cannot adjust the type of an existing variable so to take advantage of it you need to reassign to a new variable:
/** @type {InternalDockerService} */
let p = msg.payload
Once you do that, p.version
will be able to auto-suggest.
SUBFLOW
[
{
"id": "8b437750a0abc4d5",
"type": "subflow",
"name": "Set JS Types",
"info": "## Adding types\nPass in a typescript string `msg.types` and it\nwill be added to the monaco editor \nafter a browser refresh. It does not\nrequire docker to restart.\n\nThe best way to impliment this is to\nhave an inject node that runs on program\nlaunch going into a function node that\nsets the types which then pushes the value\ninto this subflow. That way the types are\nalways set.\n\n**Only works in Docker deployments**\n\nExample:\n\n```\nmsg.types = `\n/**\n * Represents an internal Docker service.\n */\ndeclare class InternalDockerService {\n /**\n * The identifier of the worker program version.\n */\n worker_programs_versions_id: number;\n\n /**\n * The version number of the worker program.\n */\n version: number;\n\n /**\n * The Docker service specification.\n */\n docker_service_spec: object;\n\n}`\n\nreturn msg;\n```\n\nHere is a quick cheat sheet on defining types:\nhttps://www.doabledanny.com/static/bf72e49df3817dde9d1d89157369c77e/70bbc/TS_light.jpg\nNote that that cheat sheet is for all of typescript\nso the second and third columns are most applicable\nto us. \n\n### Adding directly to `msg`\n\nIf you have a lot of locations that have values\nappeneded to the `msg` object, you can override\nthe default `NodeMessage` interface by directly\nassigning to it, like this.\nWhen you do this, it will automatically have the\ntype as part of `msg`. Very powerful.\n\n```\nmsg.types = `\ninterface UserData {\n userNumber: number;\n}\n\ninterface NodeMessage {\n topic?: string;\n payload?: any;\n _msgid?: string;\n userData?: UserData;\n [other: string]: any; //permit other properties\n}\n`\nreturn msg\n```\n\n## Using types\nThe editor doesn't enforce typing but does\nprovide intellisense and error checking.\nYou cannot adjust the type of an existing\nvariable so to take advantage of it you need\nto reassign to a new variable:\n\n```\n/** @type {InternalDockerService} */\nlet p = msg.payload\n```\n\nOnce you do that, `p.version` will\nbe able to auto-suggest. ",
"category": "",
"in": [
{
"x": 80,
"y": 100,
"wires": [
{
"id": "1f7abcc5096836e0"
}
]
}
],
"out": [],
"env": [],
"meta": {},
"color": "#FFAAAA",
"icon": "node-red/parser-json.svg"
},
{
"id": "e3fff956310fce55",
"type": "file in",
"z": "8b437750a0abc4d5",
"name": "",
"filename": "/usr/src/node-red/node_modules/@node-red/editor-client/public/types/node-red/func.d.ts",
"filenameType": "str",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 690,
"y": 100,
"wires": [
[
"7c52b47635adf164"
]
]
},
{
"id": "4eb94d5ec1c422ca",
"type": "comment",
"z": "8b437750a0abc4d5",
"name": "Modify this types file that is automatically included in the NodeRED Editor",
"info": "",
"x": 640,
"y": 60,
"wires": []
},
{
"id": "7c52b47635adf164",
"type": "function",
"z": "8b437750a0abc4d5",
"name": "Remove existing custom types and add in the new ones.",
"func": "let customTag = \"// --- Custom types below. Do not change this line ---\"\n\nlet lines = msg.payload.split('\\n')\n\n// Remove any pre-existing custom types\nlet indexOfCustom = lines.indexOf(customTag);\n\n// Create a new array containing only elements before the \"// -- custom\"\nlet newLines = indexOfCustom !== -1 ? lines.slice(0, indexOfCustom) : lines;\nnewLines = newLines.join('\\n')\n\nnewLines += `\n${customTag}\n\n${msg.types}\n`\n\nmsg.payload = newLines\nmsg.lines = newLines\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 590,
"y": 140,
"wires": [
[
"7f59de951a271d86"
]
]
},
{
"id": "7f59de951a271d86",
"type": "file",
"z": "8b437750a0abc4d5",
"name": "",
"filename": "/usr/src/node-red/node_modules/@node-red/editor-client/public/types/node-red/func.d.ts",
"filenameType": "str",
"appendNewline": false,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 690,
"y": 180,
"wires": [
[]
]
},
{
"id": "1f7abcc5096836e0",
"type": "function",
"z": "8b437750a0abc4d5",
"name": "Check for types string",
"func": "if (typeof msg.types != \"string\") {\n node.error(\"Trying to set types without correct `msg.types`\")\n return\n}\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 240,
"y": 100,
"wires": [
[
"e3fff956310fce55"
]
]
},
{
"id": "cd1c7e752255d2d6",
"type": "subflow:8b437750a0abc4d5",
"z": "85317cb516a8d8f5",
"name": "",
"x": 590,
"y": 1060,
"wires": []
}
]