Thanks for starting the topic @towynlin. Let me start with a status update on where I've got to with the runtime-api
branch.
The work in that branch has been focussed on teasing apart the internals of the node app that is Node-RED to provide as crisps separation as possible between logical components. This is a prelude to those logical components becoming their own node modules.
At a high-level the goal is to have separate editor and runtime node modules (there will likely be other modules split out for reuse but are less relevant to the main split).
The editor
module contains the HTML/CSS/JS resources of the editor and an express app that is used to serve them app - implementing the adminAuth
security and user management.
The runtime
module is the core of node-red responsible for instantiating nodes as per a flow configuration and running them.
Currently there is fair-to-good separation of these two components in the code base, but when you get into the details, they are still too entwined without a complete separation of concerns. This is where the new runtime-api comes in. The runtime-api is exposed by the runtime module. The editor module, when instantiated expects to be given an instance of the runtime-api. This is kinda representing in figure 1 in the following image:
Figure 2 shows how the node-red
module will take those two sub-modules and plug one straight into the other. So any request from the editor is passed straight into the runtime via the runtime-api.
Figure 3 shows how the split can be done; the editor module is given a custom implementation of the runtime-api which does whatever it wants - in this instance, some custom IPC mechanism that invokes the runtime-api exposed by the runtime module running elsewhere.
Unlike the existing, undocumented, runtime api, the new runtime-api provides a consistent design to make it easier to create custom implementations of it. In a novel approach, I've been documenting the API as I go (having added jsdoc to the build process) - you can see the full API as it currently stands here. Every function takes a single options object and returns a promise - so every function call can complete asynchronously. The options object has a user
property that will identify the user calling the API, plus any function-specific properties.
By providing the user
object, a custom implementation can make routing decisions on where to send the request to - as shown in Figure 4 below:
For completeness sake, even for scenarios where there's a single editor and single runtime running in the same node process, the same principle can be used to insert custom logic in the runtime-api path.
As a side note, we cannot lightly change what require('node-red')
returns - it may not be documented, but there's too much code out there that depends on it. I'm uneasy about using the 0.x->1.x
major version bump as an excuse to break it... although it would be legitimate if we chose to do so. It's a big decision to make.
In its current form, the runtime-api branch implies a set of modules as follows:
@node-red/editor
- contains the HTML/CSS/JS resources of the editor and an express app that is used to serve them app - implementing the adminAuth
security and user management.
@node-red/runtime
- the core runtime of node-red.
@node-red/utils
- common util code shared by runtime and editor - logging, i18n
@node-red/registry
- the runtime component responsible for loading node modules in the runtime
Chris Hiller (@boneskull) has done some great work modelling a monorepo approach. Whilst that has been done independently of the runtime-api
branch and the fine details of the modules aren't slightly different, it does a good job of showing the principle of using lerna.js
to manage the monorepo.
Current issues
So, now we get to the good stuff.... the problems I don't have solutions for yet
-
This is a big one: any node is free to expose its own HTTP routes on the httpAdmin express app we expose. This is used, for example, by the Serial node so it can retrieve a list of active serial ports when the user is configuring it. If such a node is instantiated on a runtime that is remote to the editor, when an http request comes in to the editor app for such a route we don't know how to handle it. Similarly the HTTP endpoints the Debug and Inject nodes add to handle the presses of their buttons in the editor.
There are a couple possible solutions:
a. add to the runtime-api the ability to query what routes a node has added and have a generic route handler in the editor app that is then able to proxy the request. Except... its express.js and some nodes add middleware we don't know about that goes beyond simple http GET/POST - such as serving static content the node provides.
b. hang all node-specified routes of a well-known path, so the editor app is able to proxy them en-masse.
-
How to provide the editor as an entirely static set of resources that could be served from S3 or similar.
The issue here is how to bootstrap the editor so it knows where to send its admin http requests to. The fact the editor's routes for serving static content (default icons, css etc) is muddled up with the admin routes (getting a custom node icon, deploying flows) makes separating the two a bit tricky. This isn't insurmountable - just needs a bit more poking at to find the right model.
Next steps
There are a couple threads of activity. One is to get things moved over to the monorepo structure. This is massively disruptive and will make porting fixes between the 0.18.x stream and the development branch a headache - so we need to pick the right moment to do so.
The other thread is to figure out how to address the node http routes issue.
And finally there is how to make the editor a more standalone set of static resources.