Hooking into the "_users" property of a config node's config

I've come across a bit of a weird race condition.

Under normal circumstances one might have a config node that references another config node.
In my particular situation the config node (lets call it a FOO config node) references an arbitrary number of config nodes (lets call them BAR config nodes).
The simplest way for me to deal with this is to create a table with a button that allows you to add another "BAR" config which you can select from a list. This mostly works in the sense that I can pass in the "BAR" ids into "FOO"'s config. The problem is that I somehow need to now set a dependency on the "BAR" nodes to indicate that they are being referenced by "FOO" and will be instantiated prior to "FOO" getting instantiated since "FOO"'s instantiation depends on the referenced "BAR" nodes already being instantiated.

I can see that in config nodes there is a "_users" value available, but I have no idea how to manipulate that.

Any help would be much appreciated.

As it starts with an _, it likely isn't meant to be used so should not be.

I don't have quite the same issue with uibuilder but I do need to track the URL's for each instance. I do that using a piece of common code that tracks each node as it is added - this happens on node-red startup, as well as when someone adds one in the Editor. So the table is rebuilt whenever node-red starts. Perhaps you could use a similar approach?

Your solution is similar to a case where I am scanning the flows for specific types of nodes at "flow-runtime", but this specifically related to a race condition occurring at "flow-deploytime" and has to do with the sequence in which nodes are instantiated. Nodered already has the mechanism to deal with the sequence in which the nodes are started (thanks to the users property on the node's config), and I understand that as it starts with an underscore ("") it is not intended to be manipulated directly, or relied on. What I am interested in however is to access the underlying mechanism: the node dependency graph / tree so I can either directly or indirectly attach nodes to change instantiation sequence.

Let me give a more concrete than hypothetical example.

Lets say I have a LoggerNode. I can pass a message in and it will log it to one or more configured logging backends. The logging Backends are "FileLoggerConfigNode", "ConsoleLoggerConfigNode" and "SocketLoggerConfigNode". Each of these are Config Nodes, and each of them extends a roughly "Standard" BaseLogConfigNode which does a little configuration magic when it gets initialized- consequently, by the time that LoggerNode gets initialized, any BaseLogConfigNodes that it references have to be instantiated. The way you add Logger Backends to the LoggerNode in the UI is by selecting them in a table. When the Editor for the LoggerNode is opened, I do a quick scan of the flow and find all instances of "FileLoggerConfigNode", "ConsoleLoggerConfigNode" and "SocketLoggerConfigNode" and add them to a list so they can be selected in a table of sorts. this means that you can Log a specific message to the console, 2 different files, and 3 Sockets (Not sure why you would want to do this, but that is besides the point). The problem is now that none of the instances of the configured Back-ends have a reference to the LoggerNode, which means that the startup sequence is not guaranteed, and there is no reference courting for those configurations (they will be seen as unused). I thought of just declaring 10 or 15 slots for LoggerConfigNodes, but because I will have no reference to the specific of the LoggerConfigNode type ahead of time, that plan is probably not going to work.

Well, I don't think this is how node-red nodes are meant to interact to be honest. However ...

If I've understood your requirements correctly, a variation on what I do might work. Using a plugin is handy because I believe that plugins are all loaded before nodes. Then by monitoring the nodes add/change/remove events and filtering to your config nodes, you can build a table of what has been instantiated. Using that, each node can check the table to ensure that it's dependencies have been loaded and if not, can simply delay startup - I would think that a delay of 20-200ms might be enough.

A more sophisticated approach might involve using your plugin to trigger a custom event but I haven't thought that one through fully, you might just end up with other race conditions.

Into the editor or the runtime?

I think you might be misunderstanding the problem- this is not the same issue that you dealt with in UI builder. As mentioned, I am familiar with pushing the lifecycle of nodes outside the lifecycle of the flow by maintaining their state in a plugin- I do this in several situations already, but this particular case is a race condition on the instantiation of arbitrary nodes, meaning nodes that I have not written myself. If a node does not know which nodes depend on it, then node red cannot determine the sequence in which the nodes have to be instantiated, which is why I am looking for a mechanism to tell an arbitrary confignode that a specific node depends on it. This function already exists somewhere in nodered, because nodered is already doing it... I think its just not exposed via API.

Do you mean that those nodes are not ones that you have developed yourself?

Yes and no. In my example I am using the idea of Loggers, but I want to have the capability to reference an arbitrary collection of nodes, whether they have been written by me or not... eg a list of type "mqtt-broker" ( which is part of the standard list of nodered nodes)

I managed to build a bit of a workaround- it has some limitations and it its a bit of a kludge, but it does seem to work. From what I understand, the way nodered determines the order in which to start up nodes is done using a queue-based retry loop, and not a topological sort / graph. It iterates over the node's top-level direct string properties and checks whether any of them match the ID of another config node. If a dependency is not yet active, the current node is pushed to the back of the queue and retried later. The hint here is that instead of just putting the ids of my referenced nodes in an array, I should add them directly to the defaults section. Initially I thought that there might be an issue if I did not add the type of the node to it, but it seems like that only matters if you intend to hook an actual control to it on the editor, so if the user never edits that particular value directly, the type of the referenced node doesn't matter- this also means that if you just so happen to pick a value for a particular field and that value matches the ID of another node exactly, bad things can happen, such an infinite deployment loop, but I am sure the Nodered devs are aware of that one.

The defaults section in my node properties thus looks something like this.

	.....
	.....
        loggerconfigurations: {
          value: '',
          required: true,
        },
        _loggerconfigurations_0: {
          value: '',
          required: false
        },
        _loggerconfigurations_1: {
          value: '',
          required: false
        },
        _loggerconfigurations_2: {
          value: '',
          required: false
        },
        _loggerconfigurations_3: {
          value: '',
          required: false
        },
        _loggerconfigurations_4: {
          value: '',
          required: false
        },
	......
	......

the last part is to keep the loggerconfigurations and the list of _loggerconfigurations_N in sync whenever the node is saved, and that is easily done adding this to the last part of oneditsave:

let node = this;
for (let i = 0; i < 10; i++) {
  node["_" + loggerconfigurations + "_" + i] = (node.loggerconfigurations[i] || {}).logger || "";
}

This means that the instantiation of the nodes on the flow will use _loggerconfigurations_N to determine the startup order, while I can safely ignore them in the constructor of my node and only deal with loggerconfigurations which is populated from a table on the editor.

The obvious caveat is that we now have a hardcoded number of loggerconfiguration slots. I'll just set it to a sensible value of 10 for now, until it actually comes up short. Fortunately most of the code is generated using a templating engine which makes things a whole lot easier.

Lastly, the unforseen bonus is of course that reference counting works: any node referenced by the direct properties of another node gets counted as a usage reference.