Possibly rewrite the QL engine in terms of Reactive Streams?
Summary: Not necessarily literally akka-streams, or anything like that, but at least conceptually, turning the QL engine into a streaming one makes oodles of sense.
The key insight here is to think of a QL expression as a pipeline, and make it demand-driven in the Reactive style -- pull-oriented instead of push-oriented. Each QL stage would be essentially a Flow, receiving input elements and producing output ones. Most of this would be done lazily until you get to the actual render step, which would be strict. We would need a mechanism so that InternalMethods can explicitly declare that they need to work strictly (for example, for _sort), but the vast majority shouldn't need to.
Implication: we probably wind up with a process that resembles akka-streams. We start by compiling the QL; then we run the stages, which doesn't produce the result, it materializes the pipe; then we actually feed the input into the pipe and get the result.
Implication: we stop thinking of QLContext as a static data structure, and instead think of it as a pipe. Each QLContext connects a Subscriber to a Publisher, and feeds elements between them.
Implication: most of the methods in Invocation need to become Flow-aware. Indeed, they are probably defining pipe sections rather than actually executing the transformations imperatively.
Implication: we need to become much stricter about using Invocation properly, and not cheating. I suspect that we can rewrite Invocation to work as desired with little API change, but anything that is cheating is another story. Before starting, we need to go through all InternalMethods and sanity-check them.
Implication: we probably should cache the compiled pipe with the Thing it came from. (Probably on the Model if the Instance doesn't change it.) That would likely save a ton of processing effort.
Major Advantages: the big one is that this architecture gives us a formal definition of "compiling" a QLText expression, which could then be cached. Odds are that that would speed things up enormously. It would also give us a proper abstraction for managing async Stages, which are currently horrible. And it would allow for proper lazy optimization of things like _take and _drop, removing a lot of pointless processing. (Which, once we get to pagination, will be huge.)
This not a done deal yet -- I need to investigate this very carefully. But it makes tons of sense, and is likely a huge improvement in the architecture.
To do this, we should probably fork new versions of many of the data structures to start with, and experiment heavily using unit tests.