Single instance subflow

Each time a subflow is used, it creates a new instance. Most of my subflows don't need separate instances. It would be so cool to have an option to make a subflow singleton. Not only nice to have in terms of performance, but sometimes straight up necessary.

It sounds like you need to use the link call node?
It offers much of the same benefit + your requirements?

it "looks" different - but is the same principle but with a state engine, that is shared in the flow - given its in the flow

It doesn't offer much IMO. Always 1 input, always 1 output. Takes space in flows. I want something flexible where I can choose any amount of outputs and zero input. Something that can take env vars in a nice menu. With nice icons sprinkled on too :smiley: And source code is always hidden away outside flows when I don't need to look at it. I much enjoy how subflows hide how things are done, which lets me focus in flows on what is done.

So if your intent is to stick with a subflow -you know that a subrow can still use vars outside its context - emulating a single state?

flow.get('$parent.xxxx')

I, of course am not aware of your setup, but some flexibility is not documented well IMO.
With my example above, all sub flows, can share the same state

Sure I could share state with parent flow. I'd also like to keep single instance, like a link call. Both are useful, but not the same thing.

Hey folks.

@ThingsTinkerer
I sort of feel your confusion.

subflows were around long before the link call and so I went down the subflow path because I had a LOT of repeated code in a LOT of places and updating them was painful.

The link call came out and I tried to use it and it didn't work.
(I'm not assigning blame here)
And after a lot of mucking about I did get SOME of them working and it does work for some instances where I use it now.

Yes, but you didn't exactly say anything about how many outputs you want.

I do get the takes up space argument too. Believe me: flows nowadays are way bigger than they used to be. :slight_smile:

I'm not saying either way is BETTER than the other.

But you stated SINGLE INSTANCE SUBFLOW in the title, so I'm confused.

Yes, it is nicer and neater with the subflow's node, colour and icon....
But if it is SINGLE INSTANCE...... :person_shrugging:

You can tuck it away in a corner of the flow, or..... put it in/on it's own flow.
(And you can hide that flow if you really want. But I digress.)

You may need to think a bit more about what you are wanting to do.
Again: I'm not saying it is wrong.

If you'll indulge me:
(And I'm still guilty of this myself.)

When I made flows, everything was THERE, mixed up.
The machinery of getting things done and the GUI.
(the workings and the eye candy - if you will)

And all this was done on the OLD dashboard

So if/when I update to the newer one, it is ...... painful.

It would have been easier if I kept the two parts separated in/on the tab.
That way I would just add the newer node beside the older one, test it, and then delete the older one once happy.
The way i have it there is no difference to doing it, but the nodes are WAY DOWN IN THE GUTS OF THINGS and I have to dig through the wires to get clean access to the GUI node.

So: should I redesign all the flows ti suite the new idea? I don't know.
But it is something of which I am putting a bit of thought into.

I suspect this scenario is very VERY similar to mine.

What do you want?

If it is a ONE OFF, it isn't a problem - I'm guessing - which way you go.
But it may be interesting to try both ways.
That way you (should) learn something about the workings of the other way and it may help you in the future.

Just saying.

Good luck. It is not fun when you are stuck with this kind of choice: Do I do it this way or that way?
Remember: There is no wrong way, as long as it works.

Recently i posted a feature request for collapsable groups (which is similar to your request), not sure if they give it a second thought.

1 Like

I don't understand exactly what you mean by a Singleton subflow.

A second, third and more.

I think it has benefits. A group (collapsible or not) with an input and one or more outputs would be pretty good as far as I'm concerned. It would be pretty much a portable, singleton flow with the benefit of having its own environment variables. Then if it could be called via link call, all sorts of magic starts to happen. Yes, I think it's a pretty good idea, but it's a lot of work and it's going to need a lot of thought, design, fleshing out, traction & support. Perhaps it is what link-call subroutines should have been?

1 Like

I can give one example: Made a subflow 'wrapper' for input node, or more precisely the cron node. It has menu to select interval for generating a message (an event), used to trigger something, for example I/O (read some data, then write it or send it somewhere, db, api etc.). Can configure to every 1 minute, 5 minutes, 1 hour++. It can also have option to add some flags if it is whole hour. Since subflows can't add buttons like input has, I also have an input which I can attach a normal input to, which will generate a message with a manual trigger flag (picked this up from another node and it was really useful). Maybe all this sounds useless for what you're doing, but I use it a lot.

Very close to normal nodes, but with some additional features sprinkled on top. Every time I use this subflow, it creates new instances of these nodes. But it's excessive, I only need one.

This is probably insignificant in terms of performance. But I have more complex use cases too where I need values in context to be handled "atomically" or at least very low risk of race conflicts. I was forced to use link call for this task, but it would be a lot cleaner to have it in a subflow instead.

I think the runtime creates a new instance because if there was a single one, the runtime would end up triggering all nodes connected to this singleton node, and not just the branch that started it. For example,

Considering B is the singleton subflow

A -> B -> C

D -> B -> E

When A's output flow into B, B's output would trigger both C and E, like if a cloned message teleported to another branch. However, it is expected that only C is triggered since there is no direct connection between the graphs.

1 Like

What is the functional difference between a singleton subflow and a normal subflow?

Maybe the solution is to allow developers to select multiple nodes of the same type and provide a new context menu option called “Bind.” When clicked, all selected nodes would share the same node context, credentials and configs. Each node instance would still exist separately to ensure proper message routing, but they would now share a single context store, credentials and configs, effectively behaving like a singleton.

There could also exist multiple shared instances groups of nodes of same type. For example, consider a node type B in these isolated graphs:

A -> B -> C
D -> B -> E
F -> B -> G
H -> B -> J

Nodes B from branches 1, 2, and 3 could share a context store, credentials and configs, while the B node from branch 4 share a different context store, credentials and configs.

When editing a config or credential of node that pertains to the same shared instance, all nodes of its group change as well. In memory, they all have a reference to the same config or credentials object.

A visual cue would help users identify which nodes belong to the same shared instance.

A new prop called "shared_instance_id" would be added to the exported json.

[
{
  "id": "1111111",
  "type": "B",
  "shared_instance_id": "99999999",
},
{
  "id": "1111112",
  "type": "B",
  "shared_instance_id": "999999999",
},
{
  "id": "1111113",
  "type": "B",
  "shared_instance_id": "99999999",
},
{
  "id": "1111114",
  "type": "B",
  "prop": "brazil",
},
{
  "id": "99999999",
  "type": "shared_instance",
  "prop": "uk",
}
]

A node can't be in two different shared instances at the same time.

When mapping context stores, shared_instance_id would be used instead of the node's id.

What do you guys think of this design? It would not change much of the core and it would be kind simple to implement, "I THINK"

Singleton Sublows

The concept of a ‘singleton subflow’ has been around since the day we added subflows to Node-RED - Singleton subflows · Issue #596 · node-red/node-red · GitHub - 10 years…. but hasn’t made it further (yet).

One of the challenges is, as @AllanOricil described, is that if they are all the same instance, then routing the outputs is a tricky problem to solve. It could be done with message meta-data, but that would break if, for example, a Function node generated ‘new’ message objects inside the subflow. These are the same restrictions faced by the Link-Call nodes.

Collapsing Groups

There is definitely merit to this idea, but again, there is some tricky detail that can’t just be waved away. Lets say I’ve created a large group in the middle of the workspace. I now collapse it down to a single ‘group’ node. That has left a big gap in the middle of the workspace, so I rearrange all my nodes to use that space. What happens when I expand the group again to modify its contents? It will overlap all of my other nodes. This is one of the reasons subflows open in a new tab.

A combination of the two….

I think the ultimate answer will be a combination of the two.

If we can figure out the technical implementation of singleton subflows (to solve the output routing), and all of the UX to avoid confusion between regular subflow instances and singleton subflow instances, then there would be no reason they couldn’t also be ‘callable’ by link nodes.

We already have the ‘selection to subflow’ action to create a subflow from flows you’ve already created. That works just as well turning a group into a subflow.

@AllanOricil It feels to me that is trying to solve a different set of problems here. I don’t immediately see how that would work in practice with how Nodes exist in the runtime.

My preference is (and has been for 10 years it would appear) to look at the singleton subflow approach as its a natural extension of concepts users are already familiar with.

1 Like

Do you mean your subflow let's you configure these options on each instance of the subflow? Or that you configure the node inside the subflow how you want and just add instances of the subflow where you need it?

Maybe a screenshot/export of an example flow would be helpful to understand your scenario.

With a singleton subflow approach, you wouldn't be able to set properties differently between different instances. If you edited one, those changes (properties, env vars etc) would apply to all instances by definition as thats what singleton means here.

Hmm yeah you're right. Not sure if possible, but it would have to distinguish on env variables as I make the subflow menu to select which interval to output on. Not nodes inside, they're dynamic to use the selected env settings.

Suppose we have a subflow B used multiple times in different graphs. The current setup looks like this:

Graph 1: A -> B -> C
Graph 2: D -> B -> E
Graph 3: F -> B -> G
Graph 4: H -> B -> I

Problem:

Currently, Node-RED treats each usage of subflow B as a separate instance. That means if the user wants the same behavior across graphs 1, 2, and 3, he end up creating 3 identical copies of B. Each copy has its own:

  • Message queue
  • Configuration
  • Environment variables
  • Credentials
  • Context store

This causes unnecessary duplication and makes it hard to maintain a “single source of truth.”

Current workaround:

Users sometimes manually wrap B with link nodes to reuse a single instance:

Graph 5: LI -> B -> LO

And then connect it to graphs 1, 2, and 3:

Graph 1: A -> LC5.1 -> C
Graph 2: D -> LC5.2 -> E
Graph 3: F -> LC5.3 -> G
Graph 4: H -> B -> I
Graph 5: LI -> B -> LO

Problem with this workaround:

  • Extra link call nodes (LC5.x) increase visual complexity, and possibly the runtime setup because of the extra nodes and virtual wires.
  • Graphs become harder to read and maintain. User must move around different parts of the canvas to find where the link call node leads, and then open the subflow. There is also the extra cogntive load caused by extra virtual wires drawn in the canvas. Whereas in my approach Users just have to click on the node to open its subflow, and no extra visual information, besides a simple visual cue to let users know which nodes are bound together, has to be rendered.

Proposed solution:

Instead of wrapping B with link nodes, we could allow multiple nodes to share a single subflow instance internally, when "Binding" nodes:

Graph 1: A -> B1 -> C
Graph 2: D -> B2 -> E
Graph 3: F -> B3 -> G
Graph 4: H -> B4 -> I

Where:

  • B1, B2, and B3 all reference the same internal instance of subflow B because the User selected the 3 and then clicked on "Bind"
  • B4 is a separate instance, independent of the shared one. This one is an example to show the user can still have another independent instance of the subflow B.

Benefits:

  • No extra link nodes — the graph stays compact and readable.
  • Reading the flow is easier since you dont have to move around the canvas to understand what the link call node is calling.
  • Single source of truth for multiple graphs sharing the same instance of subflow B— updates to one of the nodes that share this instance of subflow B automatically propagate to all other nodes using the same shared instance.
  • Minimal duplication — only one actual runtime instance exists for shared nodes
  • Simpler maintenance — message queues, context, and config are centralized

Essentially, each “node” can proxy messages to a shared internal subflow instance without changing the external graph structure.

What do you think? Is it clearwr now? If is not, can we do an exercise and simulate scenarios to verify if this model breaks? @knolleary

How to turn the whole type into a Singleton

If the User select only one node of any given type, another context menu called "Bind All" appears which can be used to effectively turn that type a singleton. Any node of that given type added to the canvas after turning the "type" into a singleton is bound to the same instance. The exported json has to have a new entry to enable the editor know that the type is a singleton and any new node added of that type must also be bound to the unique singleton group.

[
{
   "singletons": ["B"],
}
]

This new prop is added in the beginning of the flow.json and is an array of strings. It stores all types that were turned into a singleton.

How can Bind Selected/Bind All be implemented without breaking the core

When processing flows.json additional wires are created automatically (runtime only) to ensure routing from the B node goes into the subflow nodes, and then return to the node that is expecting the output in the right graph. For example, the B node of graphs and 2 are bound, and visually look like

Graph 1: A -> B -> C
Graph 2: D -> B -> E
Graph 3 (subflow B): in -> B.1 -> B.2 -> out

When processed, the runtime graph becomes

A    ->    B1.1 -> B1.2     -> C
     |                     |
D  ->                       -> E

obs: Bx.y

  • where x is the index of the subflow instance (remember, there can exist 1:n bound groups - when there is only one it means it is a type singleton)
  • where y is the index of the node instance inside the subflow instance

The msg is automatically injected with a core param called msg.__output_ids which is set with the Ids of the nodes connected to their respective B nodes (from the real graph) right before entering the subflow. The subflow output will determine the index of the port it has to send the msg to using msg.__output_ids and then create the right array to pass to send, for example this.send([undefined, msg]) would be used if B1.2 needs to send its output to E only, therefore selecting the right output port.

msg.__output_ids must be non enumerable, non writable, and non configurable array of strings to ensure the core implementation isn't ruined at runtime.

1 Like