Hi Dave,
Glad I have happened on a hot topic :)
In my case, since the output of the node is used in a function node, I can easily deal with a message property like msg.end. In this instance the data doesn't mean much to me anyway, so I would discard it. This is the way I was probably leaning if I 'subclassed' the tcp node for my situation.
I did wonder what the implications of sending a NULL msg were? Which nodes would see this as valid? In my use case the function that gets the msg.end would update the connection table and then send a NULL to the MQTT output node to publish a 0 length message. Subscribers would interpret this as a disconnection and act however they see fit. So in the case of a disconnect with no data in a buffer yet the NULL message works well, the intermediate function node can act on the local state, and the downstream MQTT node just does as it is told, publishes a NULL to the subscribers.
Of course this doesn't work in the case where you have persistent, or retained, topics. You don't want to push a NULL, so you would need to add an intermediate function to swallow NULLs.
I did look at the feature definitions for the various nodes and the query node is the only one that specifies a NULL msg in the case where 'they cannot provide the appropriate payload'. In the case of a broken pipe, or client disconnect, the query node cannot provide the appropriate payload so this seems consistent to me. There doesn't seem to be a specification yet for how input nodes should deal with a NULL msg, and it is probably node dependent.
Maybe on NULL they check msg.end if set use the node.warn/node.error api, if not set it is a valid NULL (for some other reason) and the flow continues?
I think that one of the beauties of node is the simplicity, nodes have input(s) and output(s). Once you have conditional outputs, or flow outside the main flow, things get complicated. Both to implement and to deal with at the user end. If a node has well defined behavior under normal and exception conditions and this doesn't suit the use case you can add function nodes intermediate to deal with the exceptional cases.
One last option that has just occurred to me is to push msg.payload = <partial>, if there is an incomplete buffer (maybe with a property msg.partial = true) then push a null payload with msg.end = true. This seems consistent with the input node description which says something like, an input node must create a new msg{} object for each distinct output. In this case downstream nodes connected to this will do as they would have done anyway and any use case that requires special handling can use a function node to do it.
Edge cases, don't you love them!